001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 The ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package org.conqat.lib.commons.region; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Set; 026import java.util.TreeSet; 027 028import org.conqat.lib.commons.collections.CollectionUtils; 029import org.conqat.lib.commons.string.StringUtils; 030 031/** 032 * Set of {@link Region} objects. Allows tests for containment on the entire set 033 * of regions. 034 */ 035public class RegionSet implements Set<Region> { 036 037 /** Name that is used if {@link RegionSet} is created without name */ 038 public static final String ANONYMOUS = "Anonymous"; 039 040 /** Name of this RegionSet */ 041 private final String name; 042 043 /** 044 * The size the map had when our internal structure was updated (or -1 to 045 * indicate a dirty state). We have to store the size rather than just a 046 * flag, as we do not catch remove calls in the iterator returned. But as 047 * you only can remove via an iterator we cannot be fooled by a sequence of 048 * add/remove calls, because we trap add in this class. 049 */ 050 private transient int cleanSize = -1; 051 052 /** The inner set we are delegating to. */ 053 private final Set<Region> inner = new TreeSet<Region>(MemberComparator.INSTANCE); 054 055 /** List of start points of the merged regions. */ 056 private transient final List<Integer> mergedStart = new ArrayList<Integer>(); 057 058 /** List of end points of the merged regions. */ 059 private transient final List<Integer> mergedEnd = new ArrayList<Integer>(); 060 061 /** 062 * Creates a named {@link RegionSet}. 063 * 064 * @param name 065 * Name of this region set. 066 */ 067 public RegionSet(String name) { 068 this.name = name; 069 } 070 071 /** Creates an unnamed region set. */ 072 public RegionSet() { 073 name = ANONYMOUS; 074 } 075 076 /** Returns the name. */ 077 public String getName() { 078 return name; 079 } 080 081 /** 082 * Returns true if the position is contained in one of the {@link Region}s 083 * in the {@link RegionSet} 084 */ 085 public boolean contains(int position) { 086 int k = getMergedIndex(position); 087 return k >= 0 && position <= mergedEnd.get(k); 088 } 089 090 /** 091 * Tests whether all of the positions of the region are contained in the 092 * {@link RegionSet} 093 */ 094 public boolean contains(Region region) { 095 int k = getMergedIndex(region.getStart()); 096 return k >= 0 && region.getEnd() <= mergedEnd.get(k); 097 } 098 099 /** 100 * Returns the index of the merged region whose start position is before or 101 * at the given position (or -1 if no such region exists). 102 */ 103 private int getMergedIndex(int position) { 104 ensureMergedUpToDate(); 105 int k = Collections.binarySearch(mergedStart, position); 106 107 // exact match, so return 108 if (k >= 0) { 109 return k; 110 } 111 112 // get insertion point (see the JavaDoc of binarySearch for an 113 // explanation of the conversion) 114 int insertionPoint = -(k + 1); 115 116 // if it would be inserted at the beginning, there is no such index 117 if (insertionPoint == 0) { 118 return -1; 119 } 120 121 // otherwise, the index must be the one before the insertion point 122 return insertionPoint - 1; 123 } 124 125 /** 126 * Tests whether any of the positions in the region are contained in the 127 * {@link RegionSet}. 128 */ 129 public boolean containsAny(Region region) { 130 // if either start or end are in, it contains "any". 131 if (contains(region.getStart()) || contains(region.getEnd())) { 132 return true; 133 } 134 135 // now we know that start and end are not contained in an interval, so 136 // to have any point contained, there must be an interval which is 137 // completely contained in the given region. But this means that the 138 // binary search has to give different results for start and end. 139 int startIndex = Collections.binarySearch(mergedStart, region.getStart()); 140 int endIndex = Collections.binarySearch(mergedStart, region.getEnd()); 141 return startIndex != endIndex; 142 } 143 144 /** Makes sure the merged regions lists are up to date. */ 145 private void ensureMergedUpToDate() { 146 if (inner.size() == cleanSize) { 147 return; 148 } 149 150 mergedStart.clear(); 151 mergedEnd.clear(); 152 153 int start = -1; 154 int end = -2; 155 for (Region region : inner) { 156 if (region.getStart() <= end + 1) { 157 end = Math.max(end, region.getEnd()); 158 } else { 159 if (start >= 0) { 160 mergedStart.add(start); 161 mergedEnd.add(end); 162 } 163 start = region.getStart(); 164 end = region.getEnd(); 165 } 166 } 167 168 if (start >= 0) { 169 mergedStart.add(start); 170 mergedEnd.add(end); 171 } 172 173 cleanSize = inner.size(); 174 } 175 176 /** 177 * Gets the number of positions contained in the RegionSet. This corresponds 178 * to the (non-overlapping) sum of the length of the regions. 179 */ 180 public int getPositionCount() { 181 ensureMergedUpToDate(); 182 int count = 0; 183 184 int size = mergedStart.size(); 185 for (int i = 0; i < size; ++i) { 186 count += mergedEnd.get(i) - mergedStart.get(i) + 1; 187 } 188 return count; 189 } 190 191 /** 192 * Returns last position in {@link RegionSet}. 193 * 194 * @throws IllegalStateException 195 * if {@link RegionSet} is empty 196 */ 197 public int getLastPosition() { 198 if (isEmpty()) { 199 throw new IllegalStateException("RegionSet is empty"); 200 } 201 202 ensureMergedUpToDate(); 203 return CollectionUtils.getLast(mergedEnd); 204 } 205 206 /** 207 * Returns first position in {@link RegionSet}. 208 * 209 * @throws IllegalStateException 210 * if {@link RegionSet} is empty 211 */ 212 public int getFirstPosition() { 213 if (isEmpty()) { 214 throw new IllegalStateException("RegionSet is empty"); 215 } 216 217 ensureMergedUpToDate(); 218 return mergedStart.get(0); 219 } 220 221 /** 222 * Creates a new {@link RegionSet} whose regions are a compactified version 223 * of this region set, i.e. the returned set is the minimal set that creates 224 * the same answers for all possible {@link #contains(int)} queries. 225 */ 226 public RegionSet createCompact() { 227 ensureMergedUpToDate(); 228 229 RegionSet compacted = new RegionSet(name); 230 for (int i = 0; i < mergedStart.size(); ++i) { 231 Region region = new Region(mergedStart.get(i), mergedEnd.get(i), name); 232 compacted.add(region); 233 } 234 return compacted; 235 } 236 237 /** 238 * Creates a new {@link RegionSet} whose regions are a complement to this 239 * {@link RegionSet}. 240 * 241 * Inversion is relative to the interval [0, last position] 242 */ 243 public RegionSet createInverted(String name, int lastPosition) { 244 ensureMergedUpToDate(); 245 246 RegionSet inverted = new RegionSet(name); 247 int lastPos = 0; 248 int size = mergedStart.size(); 249 for (int i = 0; i < size; ++i) { 250 if (mergedStart.get(i) > lastPos) { 251 inverted.add(new Region(lastPos, mergedStart.get(i) - 1, name)); 252 } 253 lastPos = mergedEnd.get(i) + 1; 254 } 255 256 if (lastPos <= lastPosition) { 257 inverted.add(new Region(lastPos, lastPosition, name)); 258 } 259 260 return inverted; 261 } 262 263 /** 264 * Returns true if both {@link RegionSet}s contain the same positions and 265 * gaps. 266 */ 267 public boolean positionsEqual(RegionSet other) { 268 if (other == null) { 269 return false; 270 } 271 272 ensureMergedUpToDate(); 273 other.ensureMergedUpToDate(); 274 275 return mergedStart.equals(other.mergedStart) && mergedEnd.equals(other.mergedEnd); 276 } 277 278 /** 279 * Comparator used for sorting the members of this set. Sorts ascending by 280 * start, then by end, then by name. 281 */ 282 private static class MemberComparator implements Comparator<Region> { 283 284 /** Unique instance of this comparator. */ 285 private final static MemberComparator INSTANCE = new MemberComparator(); 286 287 /** {@inheritDoc} */ 288 @Override 289 public int compare(Region region1, Region region2) { 290 int startDiff = region1.getStart() - region2.getStart(); 291 if (startDiff != 0) { 292 return startDiff; 293 } 294 295 int lengthDiff = region1.getLength() - region2.getLength(); 296 if (lengthDiff != 0) { 297 return lengthDiff; 298 } 299 300 return region1.getOrigin().compareTo(region2.getOrigin()); 301 } 302 } 303 304 /** {@inheritDoc} */ 305 @Override 306 public String toString() { 307 return "{" + StringUtils.concat(inner, ",") + "}"; 308 } 309 310 /** {@inheritDoc} */ 311 @Override 312 public boolean add(Region o) { 313 cleanSize = -1; 314 return inner.add(o); 315 } 316 317 /** {@inheritDoc} */ 318 @Override 319 public boolean addAll(Collection<? extends Region> c) { 320 cleanSize = -1; 321 return inner.addAll(c); 322 } 323 324 /** {@inheritDoc} */ 325 @Override 326 public void clear() { 327 cleanSize = -1; 328 inner.clear(); 329 } 330 331 /** {@inheritDoc} */ 332 @Override 333 public boolean contains(Object o) { 334 return inner.contains(o); 335 } 336 337 /** {@inheritDoc} */ 338 @Override 339 public boolean containsAll(Collection<?> c) { 340 return inner.containsAll(c); 341 } 342 343 /** {@inheritDoc} */ 344 @Override 345 public boolean equals(Object o) { 346 return inner.equals(o); 347 } 348 349 /** {@inheritDoc} */ 350 @Override 351 public int hashCode() { 352 return inner.hashCode(); 353 } 354 355 /** {@inheritDoc} */ 356 @Override 357 public boolean isEmpty() { 358 return inner.isEmpty(); 359 } 360 361 /** {@inheritDoc} */ 362 @Override 363 public Iterator<Region> iterator() { 364 return inner.iterator(); 365 } 366 367 /** {@inheritDoc} */ 368 @Override 369 public boolean remove(Object o) { 370 cleanSize = -1; 371 return inner.remove(o); 372 } 373 374 /** {@inheritDoc} */ 375 @Override 376 public boolean removeAll(Collection<?> c) { 377 cleanSize = -1; 378 return inner.removeAll(c); 379 } 380 381 /** {@inheritDoc} */ 382 @Override 383 public boolean retainAll(Collection<?> c) { 384 cleanSize = -1; 385 return inner.retainAll(c); 386 } 387 388 /** {@inheritDoc} */ 389 @Override 390 public int size() { 391 return inner.size(); 392 } 393 394 /** {@inheritDoc} */ 395 @Override 396 public Object[] toArray() { 397 return inner.toArray(); 398 } 399 400 /** {@inheritDoc} */ 401 @Override 402 public <T> T[] toArray(T[] a) { 403 return inner.toArray(a); 404 } 405}