Skip to content

Commit 7dabc90

Browse files
authored
Merge pull request #64 from CodesAway/dev-0.8
Dev 0.8
2 parents fa8d349 + 242a340 commit 7dabc90

19 files changed

Lines changed: 644 additions & 206 deletions

File tree

BEXCodeCompare/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>info.codesaway</groupId>
66
<artifactId>bex</artifactId>
7-
<version>0.7.0</version>
7+
<version>0.8.0</version>
88
<name>BEX Code Compare</name>
99
<description>Be Enhanced Code Compare</description>
1010
<properties>
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package info.codesaway.bex;
2+
3+
import static info.codesaway.bex.util.BEXUtilities.checkArgument;
4+
5+
import java.util.AbstractMap.SimpleImmutableEntry;
6+
import java.util.ArrayList;
7+
import java.util.Collections;
8+
import java.util.Comparator;
9+
import java.util.List;
10+
import java.util.Map.Entry;
11+
import java.util.Objects;
12+
import java.util.StringJoiner;
13+
14+
/**
15+
*
16+
* @param <V> type of value
17+
*/
18+
public class ImmutableIntRangeMap<V> {
19+
// Referenced Guava's ImmutableRangeMap
20+
// Based on usage in BEXMatchingUtilities
21+
private final List<IntRange> ranges;
22+
private final List<V> values;
23+
24+
private static final ImmutableIntRangeMap<Object> EMPTY = new ImmutableIntRangeMap<>(
25+
Collections.emptyList(), Collections.emptyList());
26+
27+
/** Returns an empty immutable range map. */
28+
@SuppressWarnings("unchecked")
29+
public static <V> ImmutableIntRangeMap<V> of() {
30+
return (ImmutableIntRangeMap<V>) EMPTY;
31+
}
32+
33+
private ImmutableIntRangeMap(final List<IntRange> ranges, final List<V> values) {
34+
this.ranges = ranges;
35+
this.values = values;
36+
}
37+
38+
/** Returns a new builder for an immutable range map. */
39+
public static <V> Builder<V> builder() {
40+
return new Builder<>();
41+
}
42+
43+
/**
44+
* A builder for immutable range maps. Overlapping ranges are prohibited.
45+
*/
46+
public static final class Builder<V> {
47+
private final List<Entry<IntRange, V>> entries;
48+
49+
public Builder() {
50+
this.entries = new ArrayList<>();
51+
}
52+
53+
/**
54+
* Associates the specified range with the specified value.
55+
*
56+
* @throws IllegalArgumentException if {@code range} is empty
57+
*/
58+
public Builder<V> put(final IntRange range, final V value) {
59+
Objects.requireNonNull(range);
60+
Objects.requireNonNull(value);
61+
checkArgument(!range.isEmpty(), "Range must not be empty, but was " + range);
62+
this.entries.add(new SimpleImmutableEntry<>(range.toIntBEXRange(), value));
63+
64+
return this;
65+
}
66+
67+
/**
68+
* Returns an {@code ImmutableRangeMap} containing the associations previously added to this
69+
* builder.
70+
*
71+
* @throws IllegalArgumentException if any two ranges inserted into this builder overlap
72+
*/
73+
public ImmutableIntRangeMap<V> build() {
74+
Collections.sort(this.entries, Comparator.comparingInt(e -> e.getKey().getStart()));
75+
List<IntRange> ranges = new ArrayList<>(this.entries.size());
76+
List<V> values = new ArrayList<>(this.entries.size());
77+
for (int i = 0; i < this.entries.size(); i++) {
78+
IntRange range = this.entries.get(i).getKey();
79+
if (i > 0) {
80+
IntRange prevRange = this.entries.get(i - 1).getKey();
81+
// if (range.isConnected(prevRange) && !range.intersection(prevRange).isEmpty()) {
82+
83+
// TODO: will this correctly determine if there's overlap?
84+
// TODO: what if start isn't inclusive?
85+
if (prevRange.contains(range.getStart()) || range.contains(prevRange.getStart())) {
86+
throw new IllegalArgumentException(
87+
"Overlapping ranges: range " + prevRange + " overlaps with entry " + range);
88+
}
89+
}
90+
91+
ranges.add(range);
92+
values.add(this.entries.get(i).getValue());
93+
}
94+
95+
ranges = Collections.unmodifiableList(ranges);
96+
values = Collections.unmodifiableList(values);
97+
98+
return new ImmutableIntRangeMap<>(ranges, values);
99+
}
100+
}
101+
102+
public V get(final int key) {
103+
int index = this.getIndex(key);
104+
if (index == -1) {
105+
return null;
106+
} else {
107+
IntRange range = this.ranges.get(index);
108+
return range.contains(key) ? this.values.get(index) : null;
109+
}
110+
}
111+
112+
public Entry<IntRange, V> getEntry(final int key) {
113+
int index = this.getIndex(key);
114+
if (index == -1) {
115+
return null;
116+
} else {
117+
IntRange range = this.ranges.get(index);
118+
return range.contains(key) ? new SimpleImmutableEntry<>(range, this.values.get(index)) : null;
119+
}
120+
}
121+
122+
private int getIndex(final int key) {
123+
int index = this.binarySearch(key);
124+
125+
if (index >= 0) {
126+
return index;
127+
}
128+
129+
// So, we take -index - 1 to find the insertion point
130+
// Then, we get the prior entry, since this would be the range which contained the value
131+
// If the key is before all the ranges, -1 would be returned, indicating it's not found
132+
return (-index - 1) - 1;
133+
}
134+
135+
/**
136+
* Searches the ranges for the specified value using the
137+
* binary search algorithm.
138+
*
139+
* @param key the value to be searched for
140+
* @return index of the search key, if it is contained in the array;
141+
* otherwise, <tt>(-(<i>insertion point</i>) - 1)</tt>. The
142+
* <i>insertion point</i> is defined as the point at which the
143+
* key would be inserted into the array: the index of the first
144+
* element greater than the key, or <tt>a.length</tt> if all
145+
* elements in the array are less than the specified key. Note
146+
* that this guarantees that the return value will be &gt;= 0 if
147+
* and only if the key is found.
148+
*/
149+
private int binarySearch(final int key) {
150+
// Based on Collections.indexedBinarySearch
151+
int low = 0;
152+
int high = this.ranges.size() - 1;
153+
154+
while (low <= high) {
155+
int mid = (low + high) >>> 1;
156+
int midVal = this.ranges.get(mid).getStart();
157+
int cmp = Integer.compare(midVal, key);
158+
159+
if (cmp < 0) {
160+
low = mid + 1;
161+
} else if (cmp > 0) {
162+
high = mid - 1;
163+
} else {
164+
return mid; // key found
165+
}
166+
}
167+
return -(low + 1); // key not found
168+
}
169+
170+
@Override
171+
public String toString() {
172+
StringJoiner result = new StringJoiner(", ", "{", "}");
173+
174+
for (int i = 0; i < this.ranges.size(); i++) {
175+
result.add(this.ranges.get(i) + "=" + this.values.get(i));
176+
}
177+
178+
return result.toString();
179+
}
180+
}

BEXCodeCompare/src/main/java/info/codesaway/bex/IntBEXRange.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
public final class IntBEXRange implements IntRange {
99
private final int start;
10+
private final boolean hasInclusiveStart;
1011
private final int end;
1112
private final boolean hasInclusiveEnd;
1213

@@ -16,9 +17,18 @@ public final class IntBEXRange implements IntRange {
1617
* @param end the end (exclusive)
1718
*/
1819
private IntBEXRange(final int start, final int end, final boolean hasInclusiveEnd) {
20+
this(start, true, end, hasInclusiveEnd);
21+
}
22+
23+
IntBEXRange(final int start, final boolean hasInclusiveStart, final int end, final boolean hasInclusiveEnd) {
1924
this.start = start;
25+
this.hasInclusiveStart = hasInclusiveStart;
2026
this.end = end;
2127
this.hasInclusiveEnd = hasInclusiveEnd;
28+
29+
if (end < start) {
30+
throw new IllegalArgumentException(String.format("Invalid range start = %d, end = %d", start, end));
31+
}
2232
}
2333

2434
/**
@@ -40,7 +50,7 @@ public int getEnd() {
4050
}
4151

4252
public static IntBEXRange of(final int start, final int end) {
43-
return new IntBEXRange(start, end, false);
53+
return closedOpen(start, end);
4454
}
4555

4656
/**
@@ -54,9 +64,20 @@ public static IntBEXRange closed(final int start, final int end) {
5464
return new IntBEXRange(start, end, true);
5565
}
5666

67+
/**
68+
*
69+
* @param start
70+
* @param end
71+
* @return
72+
* @since 0.8
73+
*/
74+
public static IntBEXRange closedOpen(final int start, final int end) {
75+
return new IntBEXRange(start, end, false);
76+
}
77+
5778
@Override
5879
public boolean hasInclusiveStart() {
59-
return true;
80+
return this.hasInclusiveStart;
6081
}
6182

6283
@Override
@@ -66,7 +87,7 @@ public boolean hasInclusiveEnd() {
6687

6788
@Override
6889
public String toString() {
69-
return "[" + this.getStart() + "," + this.getEnd()
90+
return "[" + this.getStart() + ".." + this.getEnd()
7091
+ (this.hasInclusiveEnd ? "]" : ")");
7192
}
7293

BEXCodeCompare/src/main/java/info/codesaway/bex/IntRange.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public default boolean contains(final int value) {
3333
|| this.hasInclusiveEnd() && value == this.getEnd();
3434
}
3535

36+
public default boolean isEmpty() {
37+
return this.getStart() == this.getEnd()
38+
&& !(this.hasInclusiveStart() && this.hasInclusiveEnd());
39+
}
40+
3641
public default IntRange canonical() {
3742
if (this.hasInclusiveStart() && !this.hasInclusiveEnd()) {
3843
return this;
@@ -42,4 +47,17 @@ public default IntRange canonical() {
4247
int end = this.hasInclusiveEnd() ? this.getEnd() + 1 : this.getEnd();
4348
return IntBEXRange.of(start, end);
4449
}
50+
51+
/**
52+
* Returns an IntBEXRange (immutable) for this IntRange
53+
* @return an IntBEXRange (immutable) for this IntRange
54+
* @since 0.8
55+
*/
56+
public default IntBEXRange toIntBEXRange() {
57+
if (this instanceof IntBEXRange) {
58+
return (IntBEXRange) this;
59+
}
60+
61+
return new IntBEXRange(this.getStart(), this.hasInclusiveStart(), this.getEnd(), this.hasInclusiveEnd());
62+
}
4563
}

0 commit comments

Comments
 (0)