blob: d2069d54b12644998f0897aa595569b482585535 [file] [log] [blame]
/*
* 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.wtk;
import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.json.JSONSerializer;
import org.apache.pivot.serialization.SerializationException;
import org.apache.pivot.util.Utils;
/**
* Class representing a range of integer values. The range includes all values
* in the interval <i>[start, end]</i>. Values may be negative, and the value of
* <tt>start</tt> may be less than or equal to the value of <tt>end</tt>.
*/
public final class Span {
public final int start;
public final int end;
public static final String START_KEY = "start";
public static final String END_KEY = "end";
/**
* Construct a new span of length 1 at the given location.
*
* @param index The start and end of this span (inclusive).
*/
public Span(int index) {
start = index;
end = index;
}
/**
* Construct a new span with the given bounds.
* @param start The start of this span - inclusive.
* @param end The end of the span - inclusive.
*/
public Span(int start, int end) {
this.start = start;
this.end = end;
}
/**
* Construct a new span from another one (a "copy
* constructor").
*
* @param span An existing span (which must not be {@code null}).
* @throws IllegalArgumentException if the given span is {@code null}.
*/
public Span(Span span) {
Utils.checkNull(span, "span");
start = span.start;
end = span.end;
}
/**
* Construct a new span from the given dictionary which must
* contain the {@link #START_KEY} and {@link #END_KEY} keys.
*
* @param span A dictionary containing start and end values.
* @throws IllegalArgumentException if the given span is {@code null}
* or if the dictionary does not contain the start and end keys.
*/
public Span(Dictionary<String, ?> span) {
Utils.checkNull(span, "span");
if (!span.containsKey(START_KEY)) {
throw new IllegalArgumentException(START_KEY + " is required.");
}
if (!span.containsKey(END_KEY)) {
throw new IllegalArgumentException(END_KEY + " is required.");
}
start = ((Integer) span.get(START_KEY)).intValue();
end = ((Integer) span.get(END_KEY)).intValue();
}
/**
* Returns the length of the span.
*
* @return The absolute value of (<tt>end</tt> minus <tt>start</tt>) + 1.
*/
public long getLength() {
return Math.abs((long) end - (long) start) + 1;
}
/**
* Determines whether this span contains another span.
*
* @param span The span to test for containment.
* @return <tt>true</tt> if this span contains <tt>span</tt>; <tt>false</tt>,
* otherwise.
* @throws IllegalArgumentException if the given span is {@code null}.
*/
public boolean contains(Span span) {
Utils.checkNull(span, "span");
Span normalizedSpan = span.normalize();
boolean contains;
if (start < end) {
contains = (start <= normalizedSpan.start && end >= normalizedSpan.end);
} else {
contains = (end <= normalizedSpan.start && start >= normalizedSpan.end);
}
return contains;
}
/**
* Determines whether this span intersects with another span.
*
* @param span The span to test for intersection.
* @return <tt>true</tt> if this span intersects with <tt>span</tt>;
* <tt>false</tt>, otherwise.
* @throws IllegalArgumentException if the given span is {@code null}.
*/
public boolean intersects(Span span) {
Utils.checkNull(span, "span");
Span normalizedSpan = span.normalize();
boolean intersects;
if (start < end) {
intersects = (start <= normalizedSpan.end && end >= normalizedSpan.start);
} else {
intersects = (end <= normalizedSpan.end && start >= normalizedSpan.start);
}
return intersects;
}
/**
* Calculates the intersection of this span and another span.
*
* @param span The span to intersect with this span.
* @return A new Span instance representing the intersection of this span and
* <tt>span</tt>, or null if the spans do not intersect.
* @throws IllegalArgumentException if the given span is {@code null}.
*/
public Span intersect(Span span) {
if (span == null) {
throw new IllegalArgumentException("span is null.");
}
Span intersection = null;
if (intersects(span)) {
intersection = new Span(Math.max(start, span.start), Math.min(end, span.end));
}
return intersection;
}
/**
* Calculates the union of this span and another span.
*
* @param span The span to union with this span.
* @return A new Span instance representing the union of this span and
* <tt>span</tt>.
*/
public Span union(Span span) {
Utils.checkNull(span, "span");
return new Span(Math.min(start, span.start), Math.max(end, span.end));
}
/**
* @return A normalized equivalent of the span in which <tt>start</tt> is
* guaranteed to be less than <tt>end</tt>.
*/
public Span normalize() {
return new Span(Math.min(start, end), Math.max(start, end));
}
/**
* Returns a new {@link Span} with both values offset by the given value.
* <p> This is useful while moving through a {@link TextPane} document
* for instance, where you have to subtract off the starting offset for
* child nodes.
*
* @param offset The positive or negative amount by which to "move" this
* span (both start and end).
* @return A new {@link Span} with updated values.
*/
public Span offset(int offset) {
return new Span(this.start + offset, this.end + offset);
}
@Override
public boolean equals(Object o) {
boolean equal = false;
if (o instanceof Span) {
Span span = (Span) o;
equal = (start == span.start && end == span.end);
}
return equal;
}
@Override
public int hashCode() {
return 31 * start + end;
}
@Override
public String toString() {
return ("{start: " + start + ", end: " + end + "}");
}
/**
* Convert a string into a span.
* <p> If the string value is a JSON map, then parse the map
* and construct using the {@link #Span(Dictionary)} method.
* <p> Otherwise the string should be a single integer value
* that will be used to construct the span using the {@link #Span(int)}
* constructor.
*
* @param value The string value to decode into a new span.
* @return The decoded span.
* @throws IllegalArgumentException if the value is {@code null} or
* if the string starts with <code>"{"</code> but it cannot be parsed as
* a JSON map.
*/
public static Span decode(String value) {
Utils.checkNull(value, "value");
Span span;
if (value.startsWith("{")) {
try {
span = new Span(JSONSerializer.parseMap(value));
} catch (SerializationException exception) {
throw new IllegalArgumentException(exception);
}
} else {
span = new Span(Integer.parseInt(value));
}
return span;
}
}