| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| ContentList |
|
| 3.4;3.4 | ||||
| ContentList$1 |
|
| 3.4;3.4 | ||||
| ContentList$CLIterator |
|
| 3.4;3.4 | ||||
| ContentList$CLListIterator |
|
| 3.4;3.4 | ||||
| ContentList$FilterList |
|
| 3.4;3.4 | ||||
| ContentList$FilterListIterator |
|
| 3.4;3.4 |
| 1 | /*-- | |
| 2 | ||
| 3 | Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. | |
| 4 | All rights reserved. | |
| 5 | ||
| 6 | Redistribution and use in source and binary forms, with or without | |
| 7 | modification, are permitted provided that the following conditions | |
| 8 | are met: | |
| 9 | ||
| 10 | 1. Redistributions of source code must retain the above copyright | |
| 11 | notice, this list of conditions, and the following disclaimer. | |
| 12 | ||
| 13 | 2. Redistributions in binary form must reproduce the above copyright | |
| 14 | notice, this list of conditions, and the disclaimer that follows | |
| 15 | these conditions in the documentation and/or other materials | |
| 16 | provided with the distribution. | |
| 17 | ||
| 18 | 3. The name "JDOM" must not be used to endorse or promote products | |
| 19 | derived from this software without prior written permission. For | |
| 20 | written permission, please contact <request_AT_jdom_DOT_org>. | |
| 21 | ||
| 22 | 4. Products derived from this software may not be called "JDOM", nor | |
| 23 | may "JDOM" appear in their name, without prior written permission | |
| 24 | from the JDOM Project Management <request_AT_jdom_DOT_org). | |
| 25 | ||
| 26 | In addition, we request (but do not require) that you include in the | |
| 27 | end-user documentation provided with the redistribution and/or in the | |
| 28 | software itself an acknowledgement equivalent to the following: | |
| 29 | "This product includes software developed by the | |
| 30 | JDOM Project (http://www.jdom.org/)." | |
| 31 | Alternatively, the acknowledgment may be graphical using the logos | |
| 32 | available at http://www.jdom.org/images/logos. | |
| 33 | ||
| 34 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |
| 35 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
| 36 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 37 | DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT | |
| 38 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 39 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 40 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |
| 41 | USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| 42 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 43 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |
| 44 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
| 45 | SUCH DAMAGE. | |
| 46 | ||
| 47 | This software consists of voluntary contributions made by many | |
| 48 | individuals on behalf of the JDOM Project and was originally | |
| 49 | created by Jason Hunter <jhunter_AT_jdom_DOT_org> and | |
| 50 | Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information | |
| 51 | on the JDOM Project, please see <http://www.jdom.org/>. | |
| 52 | ||
| 53 | */ | |
| 54 | ||
| 55 | package org.jdom2; | |
| 56 | ||
| 57 | import java.util.*; | |
| 58 | ||
| 59 | import org.jdom2.filter.*; | |
| 60 | import org.jdom2.internal.ArrayCopy; | |
| 61 | ||
| 62 | /** | |
| 63 | * A non-public list implementation holding only legal JDOM content, including | |
| 64 | * content for Document or Element nodes. Users see this class as a simple List | |
| 65 | * implementation. | |
| 66 | * | |
| 67 | * @see DocType | |
| 68 | * @see CDATA | |
| 69 | * @see Comment | |
| 70 | * @see Element | |
| 71 | * @see EntityRef | |
| 72 | * @see ProcessingInstruction | |
| 73 | * @see Text | |
| 74 | * @author Alex Rosen | |
| 75 | * @author Philippe Riand | |
| 76 | * @author Bradley S. Huffman | |
| 77 | * @author Rolf Lear | |
| 78 | */ | |
| 79 | 2764571 | final class ContentList extends AbstractList<Content> |
| 80 | implements RandomAccess { | |
| 81 | ||
| 82 | private static final int INITIAL_ARRAY_SIZE = 4; | |
| 83 | ||
| 84 | /** Our backing list */ | |
| 85 | 13973 | private Content elementData[] = null; |
| 86 | ||
| 87 | /** The amount of valid content in elementData */ | |
| 88 | private int size; | |
| 89 | ||
| 90 | /** | |
| 91 | * Completely remove references to AbstractList.modCount because in | |
| 92 | * ContentList it is confusing. As a consequence we also need to implement | |
| 93 | * a custom ListIterator for ContentList so that we don't use any of the | |
| 94 | * AbstractList iterators which use modCount.... so we have our own | |
| 95 | * ConcurrentModification checking. | |
| 96 | * | |
| 97 | */ | |
| 98 | 13973 | private transient int sizeModCount = Integer.MIN_VALUE; |
| 99 | ||
| 100 | /** | |
| 101 | * modCount is used for concurrent modification, but dataModCount is used | |
| 102 | * for refreshing filters. | |
| 103 | */ | |
| 104 | 13973 | private transient int dataModiCount = Integer.MIN_VALUE; |
| 105 | ||
| 106 | /** Document or Element this list belongs to */ | |
| 107 | private final Parent parent; | |
| 108 | ||
| 109 | /** | |
| 110 | * Force either a Document or Element parent | |
| 111 | * | |
| 112 | * @param parent | |
| 113 | * the Element this ContentList belongs to. | |
| 114 | */ | |
| 115 | 13973 | ContentList(final Parent parent) { |
| 116 | 13973 | this.parent = parent; |
| 117 | 13973 | } |
| 118 | ||
| 119 | /** | |
| 120 | * Package internal method to support building from sources that are 100% | |
| 121 | * trusted. | |
| 122 | * | |
| 123 | * @param c | |
| 124 | * content to add without any checks | |
| 125 | */ | |
| 126 | final void uncheckedAddContent(final Content c) { | |
| 127 | 8 | c.parent = parent; |
| 128 | 8 | ensureCapacity(size + 1); |
| 129 | 8 | elementData[size++] = c; |
| 130 | 8 | incModCount(); |
| 131 | 8 | } |
| 132 | ||
| 133 | /** | |
| 134 | * In the FilterList and FilterList iterators it becomes confusing as to | |
| 135 | * which modCount is being used. This formalizes the process, and using | |
| 136 | * (set/get/inc)ModCount() is the only thing you should see in the remainder | |
| 137 | * of this code. | |
| 138 | * | |
| 139 | * @param sizemod | |
| 140 | * the value to set for the size-mod count. | |
| 141 | * @param datamod | |
| 142 | * the value to set for the data-mod count. | |
| 143 | */ | |
| 144 | private final void setModCount(final int sizemod, final int datamod) { | |
| 145 | 184 | sizeModCount = sizemod; |
| 146 | 184 | dataModiCount = datamod; |
| 147 | 184 | } |
| 148 | ||
| 149 | /** | |
| 150 | * In the FilterList and FilterList iterators it becomes confusing as to | |
| 151 | * which modCount is being used. This formalizes the process, and using | |
| 152 | * (set/get/inc)ModCount() is the only thing you should see in the remainder | |
| 153 | * of this code. | |
| 154 | * | |
| 155 | * @return mod the value. | |
| 156 | */ | |
| 157 | private final int getModCount() { | |
| 158 | 625397 | return sizeModCount; |
| 159 | } | |
| 160 | ||
| 161 | /** | |
| 162 | * In the FilterList and FilterList iterators it becomes confusing as to | |
| 163 | * which modCount is being used. This formalizes the process, and using | |
| 164 | * (set/get/inc)ModCount() is the only thing you should see in the remainder | |
| 165 | * of this code. | |
| 166 | */ | |
| 167 | private final void incModCount() { | |
| 168 | // indicate there's a change to data | |
| 169 | 68214 | dataModiCount++; |
| 170 | // indicate there's a change to the size | |
| 171 | 68214 | sizeModCount++; |
| 172 | 68214 | } |
| 173 | ||
| 174 | private final void incDataModOnly() { | |
| 175 | 1770 | dataModiCount++; |
| 176 | 1770 | } |
| 177 | ||
| 178 | /** | |
| 179 | * Get the modcount of data changes. | |
| 180 | * @return the current data mode count. | |
| 181 | */ | |
| 182 | private final int getDataModCount() { | |
| 183 | 335622 | return dataModiCount; |
| 184 | } | |
| 185 | ||
| 186 | private final void checkIndex(final int index, final boolean excludes) { | |
| 187 | 181943 | final int max = excludes ? size - 1 : size; |
| 188 | ||
| 189 | 181943 | if (index < 0 || index > max) { |
| 190 | 20 | throw new IndexOutOfBoundsException("Index: " + index + |
| 191 | " Size: " + size); | |
| 192 | } | |
| 193 | ||
| 194 | 181923 | } |
| 195 | ||
| 196 | private final void checkPreConditions(final Content child, final int index, | |
| 197 | final boolean replace) { | |
| 198 | 50463 | if (child == null) { |
| 199 | 57 | throw new NullPointerException("Cannot add null object"); |
| 200 | } | |
| 201 | ||
| 202 | 50406 | checkIndex(index, replace); |
| 203 | ||
| 204 | 50398 | if (child.getParent() != null) { |
| 205 | // the content to be added already has a parent. | |
| 206 | 68 | final Parent p = child.getParent(); |
| 207 | 68 | if (p instanceof Document) { |
| 208 | 1 | throw new IllegalAddException((Element) child, |
| 209 | "The Content already has an existing parent document"); | |
| 210 | } | |
| 211 | 67 | throw new IllegalAddException( |
| 212 | "The Content already has an existing parent \"" + | |
| 213 | ((Element) p).getQualifiedName() + "\""); | |
| 214 | } | |
| 215 | ||
| 216 | 50330 | if (child == parent) { |
| 217 | 1 | throw new IllegalAddException( |
| 218 | "The Element cannot be added to itself"); | |
| 219 | } | |
| 220 | ||
| 221 | // Detect if we have <a><b><c/></b></a> and c.add(a) | |
| 222 | 50329 | if ((parent instanceof Element && child instanceof Element) && |
| 223 | ((Element) child).isAncestor((Element) parent)) { | |
| 224 | 48 | throw new IllegalAddException( |
| 225 | "The Element cannot be added as a descendent of itself"); | |
| 226 | } | |
| 227 | ||
| 228 | 50281 | } |
| 229 | ||
| 230 | /** | |
| 231 | * Check and add the <code>Content</code> to this list at the given index. | |
| 232 | * Inserts the specified object at the specified position in this list. | |
| 233 | * Shifts the object currently at that position (if any) and any subsequent | |
| 234 | * objects to the right (adds one to their indices). | |
| 235 | * | |
| 236 | * @param index | |
| 237 | * index where to add <code>Element</code> | |
| 238 | * @param child | |
| 239 | * <code>Content</code> to add | |
| 240 | */ | |
| 241 | @Override | |
| 242 | public void add(final int index, final Content child) { | |
| 243 | // Confirm basic sanity of child. | |
| 244 | 48631 | checkPreConditions(child, index, false); |
| 245 | // Check to see whether this parent believes it can contain this content | |
| 246 | 48495 | parent.canContainContent(child, index, false); |
| 247 | ||
| 248 | 48433 | child.setParent(parent); |
| 249 | ||
| 250 | 48433 | ensureCapacity(size + 1); |
| 251 | 48433 | if (index == size) { |
| 252 | 35037 | elementData[size++] = child; |
| 253 | } else { | |
| 254 | 13396 | System.arraycopy(elementData, index, elementData, index + 1, size - index); |
| 255 | 13396 | elementData[index] = child; |
| 256 | 13396 | size++; |
| 257 | } | |
| 258 | // Successful add's increment the AbstractList's modCount | |
| 259 | 48433 | incModCount(); |
| 260 | 48433 | } |
| 261 | ||
| 262 | /** | |
| 263 | * Add the specified collection to the end of this list. | |
| 264 | * | |
| 265 | * @param collection | |
| 266 | * The collection to add to the list. | |
| 267 | * @return <code>true</code> if the list was modified as a result of the | |
| 268 | * add. | |
| 269 | */ | |
| 270 | @Override | |
| 271 | public boolean addAll(final Collection<? extends Content> collection) { | |
| 272 | 34 | return addAll(size, collection); |
| 273 | } | |
| 274 | ||
| 275 | /** | |
| 276 | * Inserts the specified collection at the specified position in this list. | |
| 277 | * Shifts the object currently at that position (if any) and any subsequent | |
| 278 | * objects to the right (adds one to their indices). | |
| 279 | * | |
| 280 | * @param index | |
| 281 | * The offset to start adding the data in the collection | |
| 282 | * @param collection | |
| 283 | * The collection to insert into the list. | |
| 284 | * @return <code>true</code> if the list was modified as a result of the | |
| 285 | * add. throws IndexOutOfBoundsException if index < 0 || index > | |
| 286 | * size() | |
| 287 | */ | |
| 288 | @Override | |
| 289 | public boolean addAll(final int index, | |
| 290 | final Collection<? extends Content> collection) { | |
| 291 | 165 | if ((collection == null)) { |
| 292 | 2 | throw new NullPointerException( |
| 293 | "Can not add a null collection to the ContentList"); | |
| 294 | } | |
| 295 | ||
| 296 | 163 | checkIndex(index, false); |
| 297 | ||
| 298 | 161 | if (collection.isEmpty()) { |
| 299 | // some collections are expensive to get the size of. | |
| 300 | // use isEmpty(). | |
| 301 | 4 | return false; |
| 302 | } | |
| 303 | 157 | final int addcnt = collection.size(); |
| 304 | 157 | if (addcnt == 1) { |
| 305 | // quick check for single-add. | |
| 306 | 108 | add(index, collection.iterator().next()); |
| 307 | 40 | return true; |
| 308 | } | |
| 309 | ||
| 310 | 49 | ensureCapacity(size() + addcnt); |
| 311 | ||
| 312 | 49 | final int tmpmodcount = getModCount(); |
| 313 | 49 | final int tmpdmc = getDataModCount(); |
| 314 | 49 | boolean ok = false; |
| 315 | ||
| 316 | 49 | int count = 0; |
| 317 | ||
| 318 | try { | |
| 319 | 49 | for (Content c : collection) { |
| 320 | 169 | add(index + count, c); |
| 321 | 146 | count++; |
| 322 | 146 | } |
| 323 | 25 | ok = true; |
| 324 | } finally { | |
| 325 | 49 | if (!ok) { |
| 326 | // something failed... remove all added content | |
| 327 | 62 | while (--count >= 0) { |
| 328 | 38 | remove(index + count); |
| 329 | } | |
| 330 | // restore the mod-counts. | |
| 331 | 24 | setModCount(tmpmodcount, tmpdmc); |
| 332 | } | |
| 333 | } | |
| 334 | ||
| 335 | 25 | return true; |
| 336 | } | |
| 337 | ||
| 338 | /** | |
| 339 | * Clear the current list. | |
| 340 | */ | |
| 341 | @Override | |
| 342 | public void clear() { | |
| 343 | 142 | if (elementData != null) { |
| 344 | 235 | for (int i = 0; i < size; i++) { |
| 345 | 153 | Content obj = elementData[i]; |
| 346 | 153 | removeParent(obj); |
| 347 | } | |
| 348 | 82 | elementData = null; |
| 349 | 82 | size = 0; |
| 350 | } | |
| 351 | 142 | incModCount(); |
| 352 | 142 | } |
| 353 | ||
| 354 | /** | |
| 355 | * Clear the current list and set it to the contents of the | |
| 356 | * <code>Collection</code>. object. | |
| 357 | * | |
| 358 | * @param collection | |
| 359 | * The collection to use. | |
| 360 | */ | |
| 361 | void clearAndSet(final Collection<? extends Content> collection) { | |
| 362 | 30 | if (collection == null || collection.isEmpty()) { |
| 363 | 4 | clear(); |
| 364 | 4 | return; |
| 365 | } | |
| 366 | ||
| 367 | // keep a backup in case we need to roll-back... | |
| 368 | 26 | final Content[] old = elementData; |
| 369 | 26 | final int oldSize = size; |
| 370 | 26 | final int oldModCount = getModCount(); |
| 371 | 26 | final int oldDataModCount = getDataModCount(); |
| 372 | ||
| 373 | // clear the current system | |
| 374 | // we need to detach before we add so that we don't run in to a problem | |
| 375 | // where a content in the to-add list is one that we are 'clearing' | |
| 376 | // first. | |
| 377 | 58 | while (size > 0) { |
| 378 | 32 | old[--size].setParent(null); |
| 379 | } | |
| 380 | 26 | size = 0; |
| 381 | 26 | elementData = null; |
| 382 | ||
| 383 | 26 | boolean ok = false; |
| 384 | try { | |
| 385 | 26 | addAll(0, collection); |
| 386 | 22 | ok = true; |
| 387 | } finally { | |
| 388 | 26 | if (!ok) { |
| 389 | // we have an exception pending.... | |
| 390 | // restore the old system. | |
| 391 | // we do not need to worry about the added content | |
| 392 | // because the failed addAll will clear it up. | |
| 393 | // re-attach the old stuff | |
| 394 | 4 | elementData = old; |
| 395 | 26 | while (size < oldSize) { |
| 396 | 22 | elementData[size++].setParent(parent); |
| 397 | } | |
| 398 | 4 | setModCount(oldModCount, oldDataModCount); |
| 399 | } | |
| 400 | } | |
| 401 | ||
| 402 | 22 | } |
| 403 | ||
| 404 | /** | |
| 405 | * Increases the capacity of this <code>ContentList</code> instance, if | |
| 406 | * necessary, to ensure that it can hold at least the number of items | |
| 407 | * specified by the minimum capacity argument. | |
| 408 | * | |
| 409 | * @param minCapacity | |
| 410 | * the desired minimum capacity. | |
| 411 | */ | |
| 412 | void ensureCapacity(final int minCapacity) { | |
| 413 | 48679 | if (elementData == null) { |
| 414 | 7842 | elementData = new Content[Math.max(minCapacity, INITIAL_ARRAY_SIZE)]; |
| 415 | 7842 | return; |
| 416 | 40837 | } else if (minCapacity < elementData.length) { |
| 417 | 37252 | return; |
| 418 | } | |
| 419 | // use algorithm Wilf suggests which is essentially the same | |
| 420 | // as algorithm as ArrayList.ensureCapacity.... | |
| 421 | // typically the minCapacity is only slightly larger than | |
| 422 | // the current capacity.... so grow from the current capacity | |
| 423 | // with a double-check. | |
| 424 | 3585 | final int newcap = ((size * 3) / 2) + 1; |
| 425 | 3585 | elementData = ArrayCopy.copyOf(elementData, |
| 426 | (newcap < minCapacity ? minCapacity : newcap)); | |
| 427 | 3585 | } |
| 428 | ||
| 429 | /** | |
| 430 | * Return the object at the specified offset. | |
| 431 | * | |
| 432 | * @param index | |
| 433 | * The offset of the object. | |
| 434 | * @return The Object which was returned. | |
| 435 | */ | |
| 436 | @Override | |
| 437 | public Content get(final int index) { | |
| 438 | 109730 | checkIndex(index, true); |
| 439 | 109726 | return elementData[index]; |
| 440 | } | |
| 441 | ||
| 442 | /** | |
| 443 | * Return a view of this list based on the given filter. | |
| 444 | * | |
| 445 | * @param <E> | |
| 446 | * The Generic type of the content as set by the Filter. | |
| 447 | * @param filter | |
| 448 | * <code>Filter</code> for this view. | |
| 449 | * @return a list representing the rules of the <code>Filter</code>. | |
| 450 | */ | |
| 451 | <E extends Content> List<E> getView(final Filter<E> filter) { | |
| 452 | 7741 | return new FilterList<E>(filter); |
| 453 | } | |
| 454 | ||
| 455 | /** | |
| 456 | * Return the index of the first Element in the list. If the parent is a | |
| 457 | * <code>Document</code> then the element is the root element. If the list | |
| 458 | * contains no Elements, it returns -1. | |
| 459 | * | |
| 460 | * @return index of first element, or -1 if one doesn't exist | |
| 461 | */ | |
| 462 | int indexOfFirstElement() { | |
| 463 | 6851 | if (elementData != null) { |
| 464 | 11297 | for (int i = 0; i < size; i++) { |
| 465 | 10618 | if (elementData[i] instanceof Element) { |
| 466 | 4099 | return i; |
| 467 | } | |
| 468 | } | |
| 469 | } | |
| 470 | 2752 | return -1; |
| 471 | } | |
| 472 | ||
| 473 | /** | |
| 474 | * Return the index of the DocType element in the list. If the list contains | |
| 475 | * no DocType, it returns -1. | |
| 476 | * | |
| 477 | * @return index of the DocType, or -1 if it doesn't exist | |
| 478 | */ | |
| 479 | int indexOfDocType() { | |
| 480 | 2148 | if (elementData != null) { |
| 481 | 1998 | for (int i = 0; i < size; i++) { |
| 482 | 1394 | if (elementData[i] instanceof DocType) { |
| 483 | 376 | return i; |
| 484 | } | |
| 485 | } | |
| 486 | } | |
| 487 | 1772 | return -1; |
| 488 | } | |
| 489 | ||
| 490 | /** | |
| 491 | * Remove the object at the specified offset. | |
| 492 | * | |
| 493 | * @param index | |
| 494 | * The offset of the object. | |
| 495 | * @return The Object which was removed. | |
| 496 | */ | |
| 497 | @Override | |
| 498 | public Content remove(final int index) { | |
| 499 | 19635 | checkIndex(index, true); |
| 500 | ||
| 501 | 19631 | final Content old = elementData[index]; |
| 502 | 19631 | removeParent(old); |
| 503 | 19631 | System.arraycopy(elementData, index + 1, elementData, index, size - index - 1); |
| 504 | 19631 | elementData[--size] = null; // Let gc do its work |
| 505 | 19631 | incModCount(); |
| 506 | 19631 | return old; |
| 507 | } | |
| 508 | ||
| 509 | /** Remove the parent of a Object */ | |
| 510 | private static void removeParent(final Content c) { | |
| 511 | 21554 | c.setParent(null); |
| 512 | 21554 | } |
| 513 | ||
| 514 | /** | |
| 515 | * Set the object at the specified location to the supplied object. | |
| 516 | * | |
| 517 | * @param index | |
| 518 | * The location to set the value to. | |
| 519 | * @param child | |
| 520 | * The location to set the value to. | |
| 521 | * @return The object which was replaced. throws IndexOutOfBoundsException | |
| 522 | * if index < 0 || index >= size() | |
| 523 | */ | |
| 524 | @Override | |
| 525 | public Content set(final int index, final Content child) { | |
| 526 | // Confirm basic sanity of child. | |
| 527 | 1832 | checkPreConditions(child, index, true); |
| 528 | ||
| 529 | // Ensure the detail checks out OK too. | |
| 530 | 1786 | parent.canContainContent(child, index, true); |
| 531 | ||
| 532 | /* | |
| 533 | * Do a special case of set() where we don't do a remove() then add() | |
| 534 | * because that affects the modCount. We want to do a true set(). See | |
| 535 | * issue #15 | |
| 536 | */ | |
| 537 | ||
| 538 | 1770 | final Content old = elementData[index]; |
| 539 | 1770 | removeParent(old); |
| 540 | 1770 | child.setParent(parent); |
| 541 | 1770 | elementData[index] = child; |
| 542 | // for set method we increment dataModCount, but not modCount | |
| 543 | // set does not change the structure of the List (size()) | |
| 544 | 1770 | incDataModOnly(); |
| 545 | 1770 | return old; |
| 546 | } | |
| 547 | ||
| 548 | /** | |
| 549 | * Return the number of items in this list | |
| 550 | * | |
| 551 | * @return The number of items in this list. | |
| 552 | */ | |
| 553 | @Override | |
| 554 | public int size() { | |
| 555 | 64455 | return size; |
| 556 | } | |
| 557 | ||
| 558 | @Override | |
| 559 | public Iterator<Content> iterator() { | |
| 560 | 30461 | return new CLIterator(); |
| 561 | } | |
| 562 | ||
| 563 | @Override | |
| 564 | public ListIterator<Content> listIterator() { | |
| 565 | 407 | return new CLListIterator(0); |
| 566 | } | |
| 567 | ||
| 568 | @Override | |
| 569 | public ListIterator<Content> listIterator(final int start) { | |
| 570 | 1602 | return new CLListIterator(start); |
| 571 | } | |
| 572 | ||
| 573 | /** | |
| 574 | * Return this list as a <code>String</code> | |
| 575 | * | |
| 576 | * @return The String representation of this list. | |
| 577 | */ | |
| 578 | @Override | |
| 579 | public String toString() { | |
| 580 | 73 | return super.toString(); |
| 581 | } | |
| 582 | ||
| 583 | private void sortInPlace(final int[] indexes) { | |
| 584 | // the indexes are a discrete set of values that have no duplicates, | |
| 585 | // and describe the relative order of each of them. | |
| 586 | // as a result, we can do some tricks.... | |
| 587 | 15 | final int[] unsorted = ArrayCopy.copyOf(indexes, indexes.length); |
| 588 | 15 | Arrays.sort(unsorted); |
| 589 | 15 | final Content[] usc = new Content[unsorted.length]; |
| 590 | 76 | for (int i = 0; i < usc.length; i++) { |
| 591 | 61 | usc[i] = elementData[indexes[i]]; |
| 592 | } | |
| 593 | // usc contains the content in their pre-sorted order.... | |
| 594 | 76 | for (int i = 0; i < indexes.length; i ++) { |
| 595 | 61 | elementData[unsorted[i]] = usc[i]; |
| 596 | } | |
| 597 | 15 | } |
| 598 | ||
| 599 | /** | |
| 600 | * Unlike the Arrays.binarySearch, this method never expects an | |
| 601 | * "already exists" condition, we only ever add, thus there will never | |
| 602 | * be a negative insertion-point. | |
| 603 | * @param indexes THe pointers to search within | |
| 604 | * @param len The number of pointers to search within | |
| 605 | * @param val The pointer we are checking for. | |
| 606 | * @param comp The Comparator to compare with | |
| 607 | * @return the insertion point. | |
| 608 | */ | |
| 609 | private final int binarySearch(final int[] indexes, final int len, | |
| 610 | final int val, final Comparator<? super Content> comp) { | |
| 611 | 20 | int left = 0, mid = 0, right = len - 1, cmp = 0; |
| 612 | 20 | final Content base = elementData[val]; |
| 613 | 35 | while (left <= right) { |
| 614 | 21 | mid = (left + right) >>> 1; |
| 615 | 21 | cmp = comp.compare(base, elementData[indexes[mid]]); |
| 616 | 21 | if (cmp == 0) { |
| 617 | 9 | while (cmp == 0 && mid < right && comp.compare( |
| 618 | base, elementData[indexes[mid + 1]]) == 0) { | |
| 619 | 3 | mid++; |
| 620 | } | |
| 621 | 6 | return mid + 1; |
| 622 | 15 | } else if (cmp < 0) { |
| 623 | 8 | right = mid - 1; |
| 624 | } else { | |
| 625 | 7 | left = mid + 1; |
| 626 | } | |
| 627 | } | |
| 628 | 14 | return left; |
| 629 | } | |
| 630 | ||
| 631 | /** | |
| 632 | * Sorts this list using the supplied Comparator to compare elements. | |
| 633 | * | |
| 634 | * @param comp - the Comparator used to compare list elements. A null value indicates that the elements' natural ordering should be used | |
| 635 | * @Since 2.0.6 | |
| 636 | */ | |
| 637 | // @Override - only in Java8 | |
| 638 | public final void sort(final Comparator<? super Content> comp) { | |
| 639 | ||
| 640 | 6 | if (comp == null) { |
| 641 | // sort by the 'natural order', which, there is none. | |
| 642 | // options, throw exception, or let the current-order represent the natural order. | |
| 643 | // do nothing is the better alternative. | |
| 644 | 1 | return; |
| 645 | } | |
| 646 | ||
| 647 | 5 | final int sz = size; |
| 648 | 5 | int[] indexes = new int[sz]; |
| 649 | 25 | for (int i = 0 ; i < sz; i++) { |
| 650 | 20 | final int ip = binarySearch(indexes, i, i, comp); |
| 651 | 20 | if (ip < i) { |
| 652 | 9 | System.arraycopy(indexes, ip, indexes, ip+1, i - ip); |
| 653 | } | |
| 654 | 20 | indexes[ip] = i; |
| 655 | } | |
| 656 | 5 | sortInPlace(indexes); |
| 657 | 5 | } |
| 658 | ||
| 659 | /* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * * * */ | |
| 660 | /* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * * * */ | |
| 661 | /** | |
| 662 | * A fast implementation of Iterator. | |
| 663 | * <p> | |
| 664 | * It is fast because it is tailored to the ContentList, and not the | |
| 665 | * flexible implementation used by AbstractList. It needs to be fast because | |
| 666 | * iterator() is used extensively in the for-each type loop. | |
| 667 | * | |
| 668 | * @author Rolf Lear | |
| 669 | */ | |
| 670 | 397875 | private final class CLIterator implements Iterator<Content> { |
| 671 | 30461 | private int expect = -1; |
| 672 | 30461 | private int cursor = 0; |
| 673 | 30461 | private boolean canremove = false; |
| 674 | ||
| 675 | 30461 | private CLIterator() { |
| 676 | 30461 | expect = getModCount(); |
| 677 | 30461 | } |
| 678 | ||
| 679 | @Override | |
| 680 | public boolean hasNext() { | |
| 681 | 413966 | return cursor < size; |
| 682 | } | |
| 683 | ||
| 684 | @Override | |
| 685 | public Content next() { | |
| 686 | 367414 | if (getModCount() != expect) { |
| 687 | 2 | throw new ConcurrentModificationException("ContentList was " + |
| 688 | "modified outside of this Iterator"); | |
| 689 | } | |
| 690 | 367412 | if (cursor >= size) { |
| 691 | 1092 | throw new NoSuchElementException("Iterated beyond the end of " + |
| 692 | "the ContentList."); | |
| 693 | } | |
| 694 | 366320 | canremove = true; |
| 695 | 366320 | return elementData[cursor++]; |
| 696 | } | |
| 697 | ||
| 698 | @Override | |
| 699 | public void remove() { | |
| 700 | 716 | if (getModCount() != expect) { |
| 701 | 1 | throw new ConcurrentModificationException("ContentList was " + |
| 702 | "modified outside of this Iterator"); | |
| 703 | } | |
| 704 | 715 | if (!canremove) { |
| 705 | 384 | throw new IllegalStateException("Can only remove() content " + |
| 706 | "after a call to next()"); | |
| 707 | } | |
| 708 | 331 | canremove = false; |
| 709 | 331 | ContentList.this.remove(--cursor); |
| 710 | 331 | expect = getModCount(); |
| 711 | 331 | } |
| 712 | ||
| 713 | } | |
| 714 | ||
| 715 | /* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * * * */ | |
| 716 | /* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * * * */ | |
| 717 | /** | |
| 718 | * A fast implementation of Iterator. | |
| 719 | * <p> | |
| 720 | * It is fast because it is tailored to the ContentList, and not the | |
| 721 | * flexible implementation used by AbstractList. It needs to be fast because | |
| 722 | * iterator() is used extensively in the for-each type loop. | |
| 723 | * | |
| 724 | * @author Rolf Lear | |
| 725 | */ | |
| 726 | 16435 | private final class CLListIterator implements ListIterator<Content> { |
| 727 | /** Whether this iterator is in forward or reverse. */ | |
| 728 | 2009 | private boolean forward = false; |
| 729 | /** Whether a call to remove() is valid */ | |
| 730 | 2009 | private boolean canremove = false; |
| 731 | /** Whether a call to set() is valid */ | |
| 732 | 2009 | private boolean canset = false; |
| 733 | ||
| 734 | /** Expected modCount in our backing list */ | |
| 735 | 2009 | private int expectedmod = -1; |
| 736 | ||
| 737 | 2009 | private int cursor = -1; |
| 738 | ||
| 739 | /** | |
| 740 | * Default constructor | |
| 741 | * | |
| 742 | * @param flist | |
| 743 | * The FilterList over which we will iterate. | |
| 744 | * @param start | |
| 745 | * where in the FilterList to start iteration. | |
| 746 | */ | |
| 747 | 2009 | CLListIterator(final int start) { |
| 748 | 2009 | expectedmod = getModCount(); |
| 749 | // always start list iterators in backward mode .... | |
| 750 | // it makes sense... really. | |
| 751 | 2009 | forward = false; |
| 752 | ||
| 753 | 2009 | checkIndex(start, false); |
| 754 | ||
| 755 | 2007 | cursor = start; |
| 756 | 2007 | } |
| 757 | ||
| 758 | private void checkConcurrent() { | |
| 759 | 21786 | if (expectedmod != getModCount()) { |
| 760 | 5 | throw new ConcurrentModificationException("The ContentList " + |
| 761 | "supporting this iterator has been modified by" + | |
| 762 | "something other than this Iterator."); | |
| 763 | } | |
| 764 | 21781 | } |
| 765 | ||
| 766 | /** | |
| 767 | * Returns <code>true</code> if this list iterator has a next element. | |
| 768 | */ | |
| 769 | @Override | |
| 770 | public boolean hasNext() { | |
| 771 | 6242 | return (forward ? cursor + 1 : cursor) < size; |
| 772 | } | |
| 773 | ||
| 774 | /** | |
| 775 | * Returns <code>true</code> if this list iterator has more elements | |
| 776 | * when traversing the list in the reverse direction. | |
| 777 | */ | |
| 778 | @Override | |
| 779 | public boolean hasPrevious() { | |
| 780 | 5897 | return (forward ? cursor : cursor - 1) >= 0; |
| 781 | } | |
| 782 | ||
| 783 | /** | |
| 784 | * Returns the index of the element that would be returned by a | |
| 785 | * subsequent call to <code>next</code>. | |
| 786 | */ | |
| 787 | @Override | |
| 788 | public int nextIndex() { | |
| 789 | 15624 | return forward ? cursor + 1 : cursor; |
| 790 | } | |
| 791 | ||
| 792 | /** | |
| 793 | * Returns the index of the element that would be returned by a | |
| 794 | * subsequent call to <code>previous</code>. (Returns -1 if the list | |
| 795 | * iterator is at the beginning of the list.) | |
| 796 | */ | |
| 797 | @Override | |
| 798 | public int previousIndex() { | |
| 799 | 14060 | return forward ? cursor : cursor - 1; |
| 800 | } | |
| 801 | ||
| 802 | /** | |
| 803 | * Returns the next element in the list. | |
| 804 | */ | |
| 805 | @Override | |
| 806 | public Content next() { | |
| 807 | 5716 | checkConcurrent(); |
| 808 | 5715 | final int next = forward ? cursor + 1 : cursor; |
| 809 | ||
| 810 | 5715 | if (next >= size) { |
| 811 | 606 | throw new NoSuchElementException("next() is beyond the end of the Iterator"); |
| 812 | } | |
| 813 | ||
| 814 | 5109 | cursor = next; |
| 815 | 5109 | forward = true; |
| 816 | 5109 | canremove = true; |
| 817 | 5109 | canset = true; |
| 818 | 5109 | return elementData[cursor]; |
| 819 | } | |
| 820 | ||
| 821 | /** | |
| 822 | * Returns the previous element in the list. | |
| 823 | */ | |
| 824 | @Override | |
| 825 | public Content previous() { | |
| 826 | 7214 | checkConcurrent(); |
| 827 | 7213 | final int prev = forward ? cursor : cursor - 1; |
| 828 | ||
| 829 | 7213 | if (prev < 0) { |
| 830 | 606 | throw new NoSuchElementException("previous() is beyond the beginning of the Iterator"); |
| 831 | } | |
| 832 | ||
| 833 | 6607 | cursor = prev; |
| 834 | 6607 | forward = false; |
| 835 | 6607 | canremove = true; |
| 836 | 6607 | canset = true; |
| 837 | 6607 | return elementData[cursor]; |
| 838 | } | |
| 839 | ||
| 840 | /** | |
| 841 | * Inserts the specified element into the list . | |
| 842 | */ | |
| 843 | @Override | |
| 844 | public void add(final Content obj) { | |
| 845 | 3000 | checkConcurrent(); |
| 846 | // always add before what would normally be returned by next(); | |
| 847 | 2999 | final int next = forward ? cursor + 1 : cursor; |
| 848 | ||
| 849 | 2999 | ContentList.this.add(next, obj); |
| 850 | ||
| 851 | 2985 | expectedmod = getModCount(); |
| 852 | ||
| 853 | 2985 | canremove = canset = false; |
| 854 | ||
| 855 | // a call to next() should be unaffected, so, whatever was going to | |
| 856 | // be next will still be next, remember, what was going to be next | |
| 857 | // has been shifted 'right' by our insert. | |
| 858 | // we ensure this by setting the cursor to next(), and making it | |
| 859 | // forward | |
| 860 | 2985 | cursor = next; |
| 861 | 2985 | forward = true; |
| 862 | 2985 | } |
| 863 | ||
| 864 | /** | |
| 865 | * Removes from the list the last element that was returned by the last | |
| 866 | * call to <code>next</code> or <code>previous</code>. | |
| 867 | */ | |
| 868 | @Override | |
| 869 | public void remove() { | |
| 870 | 5379 | checkConcurrent(); |
| 871 | 5378 | if (!canremove) |
| 872 | 2394 | throw new IllegalStateException("Can not remove an " |
| 873 | + "element unless either next() or previous() has been called " | |
| 874 | + "since the last remove()"); | |
| 875 | // we are removing the last entry returned by either next() or | |
| 876 | // previous(). | |
| 877 | // the idea is to remove it, and pretend that we used to be at the | |
| 878 | // entry that happened *after* the removed entry. | |
| 879 | // so, get what would be the next entry (set at tmpcursor). | |
| 880 | // so call nextIndex to set tmpcursor to what would come after. | |
| 881 | 2984 | ContentList.this.remove(cursor); |
| 882 | 2984 | forward = false; |
| 883 | 2984 | expectedmod = getModCount(); |
| 884 | ||
| 885 | 2984 | canremove = false; |
| 886 | 2984 | canset = false; |
| 887 | 2984 | } |
| 888 | ||
| 889 | /** | |
| 890 | * Replaces the last element returned by <code>next</code> or | |
| 891 | * <code>previous</code> with the specified element. | |
| 892 | */ | |
| 893 | @Override | |
| 894 | public void set(final Content obj) { | |
| 895 | 477 | checkConcurrent(); |
| 896 | 476 | if (!canset) { |
| 897 | 128 | throw new IllegalStateException("Can not set an element " |
| 898 | + "unless either next() or previous() has been called since the " | |
| 899 | + "last remove() or set()"); | |
| 900 | } | |
| 901 | ||
| 902 | 348 | ContentList.this.set(cursor, obj); |
| 903 | 334 | expectedmod = getModCount(); |
| 904 | ||
| 905 | 334 | } |
| 906 | ||
| 907 | } | |
| 908 | ||
| 909 | /* * * * * * * * * * * * * FilterList * * * * * * * * * * * * * * * */ | |
| 910 | /* * * * * * * * * * * * * FilterList * * * * * * * * * * * * * * * */ | |
| 911 | ||
| 912 | /** | |
| 913 | * <code>FilterList</code> represents legal JDOM content, including content | |
| 914 | * for <code>Document</code>s or <code>Element</code>s. | |
| 915 | * <p> | |
| 916 | * FilterList represents a dynamic view of the backing ContentList, changes | |
| 917 | * to the backing list are reflected in the FilterList, and visa-versa. | |
| 918 | * | |
| 919 | * @param <F> | |
| 920 | * The Generic type of content accepted by the underlying Filter. | |
| 921 | */ | |
| 922 | ||
| 923 | 156270 | class FilterList<F extends Content> extends AbstractList<F> { |
| 924 | ||
| 925 | // The filter to apply | |
| 926 | final Filter<F> filter; | |
| 927 | // correlate the position in the filtered list to the index in the | |
| 928 | // backing ContentList. | |
| 929 | 7741 | int[] backingpos = new int[size + INITIAL_ARRAY_SIZE]; |
| 930 | 7741 | int backingsize = 0; |
| 931 | // track data modifications in the backing ContentList. | |
| 932 | 7741 | int xdata = -1; |
| 933 | ||
| 934 | /** | |
| 935 | * Create a new instance of the FilterList with the specified Filter. | |
| 936 | * | |
| 937 | * @param filter | |
| 938 | * The underlying Filter to use for filtering the content. | |
| 939 | */ | |
| 940 | 7741 | FilterList(final Filter<F> filter) { |
| 941 | 7741 | this.filter = filter; |
| 942 | 7741 | } |
| 943 | ||
| 944 | /** | |
| 945 | * Returns true if there is no content in this FilterList. | |
| 946 | * @return true if there is no content in this FilterList | |
| 947 | */ | |
| 948 | @Override | |
| 949 | public boolean isEmpty() { | |
| 950 | // More efficient implementation than default size() == 0 | |
| 951 | // we use resync() to accomplish the task. If there is an | |
| 952 | // element 0 in this FilterList, then it is not empty! | |
| 953 | // we may already have resync'd 0, which will be a fast return then, | |
| 954 | // or, if we have not resync'd 0, then we only have to filter up to | |
| 955 | // the first matching element to get a result (or the whole list | |
| 956 | // if isEmpty() is true). | |
| 957 | 578 | return resync(0) == size; |
| 958 | } | |
| 959 | ||
| 960 | /** | |
| 961 | * Synchronise our view to the backing list. Only synchronise the first | |
| 962 | * <code>index</code> view elements. For want of a better word, we'll | |
| 963 | * call this a 'Lazy' implementation. | |
| 964 | * | |
| 965 | * @param index | |
| 966 | * how much we want to sync. Set to -1 to synchronise everything. | |
| 967 | * @return the index in the backing array of the <i>index'th</i> match. | |
| 968 | * or the backing data size if there is no match for the index. | |
| 969 | */ | |
| 970 | private final int resync(final int index) { | |
| 971 | 293484 | if (xdata != getDataModCount()) { |
| 972 | // The underlying list was modified somehow... | |
| 973 | // we need to invalidate our research... | |
| 974 | 7847 | xdata = getDataModCount(); |
| 975 | 7847 | backingsize = 0; |
| 976 | 7847 | if (size >= backingpos.length) { |
| 977 | 1 | backingpos = new int[size + 1]; |
| 978 | } | |
| 979 | } | |
| 980 | ||
| 981 | 293484 | if (index >= 0 && index < backingsize) { |
| 982 | // we have already indexed far enough... | |
| 983 | // return the backing index. | |
| 984 | 213413 | return backingpos[index]; |
| 985 | } | |
| 986 | ||
| 987 | // the index in the backing list of the next value to check. | |
| 988 | 80071 | int bpi = 0; |
| 989 | 80071 | if (backingsize > 0) { |
| 990 | 62996 | bpi = backingpos[backingsize - 1] + 1; |
| 991 | } | |
| 992 | ||
| 993 | 137076 | while (bpi < size) { |
| 994 | 103165 | final F gotit = filter.filter(elementData[bpi]); |
| 995 | 103165 | if (gotit != null) { |
| 996 | 47103 | backingpos[backingsize] = bpi; |
| 997 | 47103 | if (backingsize++ == index) { |
| 998 | 46160 | return bpi; |
| 999 | } | |
| 1000 | } | |
| 1001 | 57005 | bpi++; |
| 1002 | 57005 | } |
| 1003 | 33911 | return size; |
| 1004 | } | |
| 1005 | ||
| 1006 | /** | |
| 1007 | * Inserts the specified object at the specified position in this list. | |
| 1008 | * Shifts the object currently at that position (if any) and any | |
| 1009 | * subsequent objects to the right (adds one to their indices). | |
| 1010 | * | |
| 1011 | * @param index | |
| 1012 | * The location to set the value to. | |
| 1013 | * @param obj | |
| 1014 | * The object to insert into the list. throws | |
| 1015 | * IndexOutOfBoundsException if index < 0 || index > size() | |
| 1016 | */ | |
| 1017 | @Override | |
| 1018 | public void add(final int index, final Content obj) { | |
| 1019 | 16495 | if (index < 0) { |
| 1020 | 4 | throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); |
| 1021 | } | |
| 1022 | 16491 | int adj = resync(index); |
| 1023 | 16491 | if (adj == size && index > size()) { |
| 1024 | 4 | throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); |
| 1025 | } | |
| 1026 | 16487 | if (filter.matches(obj)) { |
| 1027 | 16390 | ContentList.this.add(adj, obj); |
| 1028 | ||
| 1029 | // we can optimise the laziness now by doing a partial reset on | |
| 1030 | // the backing list... invalidate everything *after* the added | |
| 1031 | // content | |
| 1032 | 16355 | if (backingpos.length <= size) { |
| 1033 | 341 | backingpos = ArrayCopy.copyOf(backingpos, backingpos.length + 1); |
| 1034 | } | |
| 1035 | 16355 | backingpos[index] = adj; |
| 1036 | 16355 | backingsize = index + 1; |
| 1037 | 16355 | xdata = getDataModCount(); |
| 1038 | ||
| 1039 | } else { | |
| 1040 | 97 | throw new IllegalAddException("Filter won't allow the " + |
| 1041 | obj.getClass().getName() + | |
| 1042 | " '" + obj + "' to be added to the list"); | |
| 1043 | } | |
| 1044 | 16355 | } |
| 1045 | ||
| 1046 | @Override | |
| 1047 | public boolean addAll(final int index, | |
| 1048 | final Collection<? extends F> collection) { | |
| 1049 | 201 | if (collection == null) { |
| 1050 | 3 | throw new NullPointerException("Cannot add a null collection"); |
| 1051 | } | |
| 1052 | ||
| 1053 | 198 | if (index < 0) { |
| 1054 | 3 | throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); |
| 1055 | } | |
| 1056 | ||
| 1057 | 195 | final int adj = resync(index); |
| 1058 | 195 | if (adj == size && index > size()) { |
| 1059 | 3 | throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); |
| 1060 | } | |
| 1061 | ||
| 1062 | 192 | final int addcnt = collection.size(); |
| 1063 | 192 | if (addcnt == 0) { |
| 1064 | 3 | return false; |
| 1065 | } | |
| 1066 | ||
| 1067 | 189 | ContentList.this.ensureCapacity(ContentList.this.size() + addcnt); |
| 1068 | ||
| 1069 | 189 | final int tmpmodcount = getModCount(); |
| 1070 | 189 | final int tmpdmc = getDataModCount(); |
| 1071 | 189 | boolean ok = false; |
| 1072 | ||
| 1073 | 189 | int count = 0; |
| 1074 | ||
| 1075 | try { | |
| 1076 | 189 | for (Content c : collection) { |
| 1077 | 305 | if (c == null) { |
| 1078 | 63 | throw new NullPointerException( |
| 1079 | "Cannot add null content"); | |
| 1080 | } | |
| 1081 | 242 | if (filter.matches(c)) { |
| 1082 | 198 | ContentList.this.add(adj + count, c); |
| 1083 | // we can optimise the laziness now by doing a partial | |
| 1084 | // reset on | |
| 1085 | // the backing list... invalidate everything *after* the | |
| 1086 | // added | |
| 1087 | // content | |
| 1088 | 149 | if (backingpos.length <= size) { |
| 1089 | 5 | backingpos = ArrayCopy.copyOf(backingpos, backingpos.length + addcnt); |
| 1090 | } | |
| 1091 | 149 | backingpos[index + count] = adj + count; |
| 1092 | 149 | backingsize = index + count + 1; |
| 1093 | 149 | xdata = getDataModCount(); |
| 1094 | ||
| 1095 | 149 | count++; |
| 1096 | } else { | |
| 1097 | 44 | throw new IllegalAddException("Filter won't allow the " + |
| 1098 | c.getClass().getName() + | |
| 1099 | " '" + c + "' to be added to the list"); | |
| 1100 | ||
| 1101 | } | |
| 1102 | 149 | } |
| 1103 | 33 | ok = true; |
| 1104 | } finally { | |
| 1105 | 189 | if (!ok) { |
| 1106 | // something failed... remove all added content | |
| 1107 | 232 | while (--count >= 0) { |
| 1108 | 76 | ContentList.this.remove(adj + count); |
| 1109 | } | |
| 1110 | // restore the mod-counts. | |
| 1111 | 156 | setModCount(tmpmodcount, tmpdmc); |
| 1112 | // reset the cache... will need to redo some work on another | |
| 1113 | // call maybe.... | |
| 1114 | 156 | backingsize = index; |
| 1115 | 156 | xdata = tmpmodcount; |
| 1116 | } | |
| 1117 | } | |
| 1118 | ||
| 1119 | 33 | return true; |
| 1120 | } | |
| 1121 | ||
| 1122 | /** | |
| 1123 | * Return the object at the specified offset. | |
| 1124 | * | |
| 1125 | * @param index | |
| 1126 | * The offset of the object. | |
| 1127 | * @return The Object which was returned. | |
| 1128 | */ | |
| 1129 | @Override | |
| 1130 | public F get(final int index) { | |
| 1131 | 94641 | if (index < 0) { |
| 1132 | 4 | throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); |
| 1133 | } | |
| 1134 | 94637 | final int adj = resync(index); |
| 1135 | 94637 | if (adj == size) { |
| 1136 | 4 | throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); |
| 1137 | } | |
| 1138 | 94633 | return filter.filter(ContentList.this.get(adj)); |
| 1139 | } | |
| 1140 | ||
| 1141 | @Override | |
| 1142 | public Iterator<F> iterator() { | |
| 1143 | 10738 | return new FilterListIterator<F>(this, 0); |
| 1144 | } | |
| 1145 | ||
| 1146 | @Override | |
| 1147 | public ListIterator<F> listIterator() { | |
| 1148 | 1286 | return new FilterListIterator<F>(this, 0); |
| 1149 | } | |
| 1150 | ||
| 1151 | @Override | |
| 1152 | public ListIterator<F> listIterator(final int index) { | |
| 1153 | 6279 | return new FilterListIterator<F>(this, index); |
| 1154 | } | |
| 1155 | ||
| 1156 | /** | |
| 1157 | * Remove the object at the specified offset. | |
| 1158 | * | |
| 1159 | * @param index | |
| 1160 | * The offset of the object. | |
| 1161 | * @return The Object which was removed. | |
| 1162 | */ | |
| 1163 | @Override | |
| 1164 | public F remove(final int index) { | |
| 1165 | 16110 | if (index < 0) { |
| 1166 | 4 | throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); |
| 1167 | } | |
| 1168 | 16106 | final int adj = resync(index); |
| 1169 | 16106 | if (adj == size) { |
| 1170 | 4 | throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); |
| 1171 | } | |
| 1172 | 16102 | final Content oldc = ContentList.this.remove(adj); |
| 1173 | // optimise the backing cache. | |
| 1174 | 16102 | backingsize = index; |
| 1175 | 16102 | xdata = getDataModCount(); |
| 1176 | // use Filter to ensure the cast is right. | |
| 1177 | 16102 | return filter.filter(oldc); |
| 1178 | } | |
| 1179 | ||
| 1180 | /** | |
| 1181 | * Set the object at the specified location to the supplied object. | |
| 1182 | * | |
| 1183 | * @param index | |
| 1184 | * The location to set the value to. | |
| 1185 | * @param obj | |
| 1186 | * The location to set the value to. | |
| 1187 | * @return The object which was replaced. throws | |
| 1188 | * IndexOutOfBoundsException if index < 0 || index >= size() | |
| 1189 | */ | |
| 1190 | @Override | |
| 1191 | public F set(final int index, final F obj) { | |
| 1192 | 1532 | if (index < 0) { |
| 1193 | 4 | throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); |
| 1194 | } | |
| 1195 | 1528 | final int adj = resync(index); |
| 1196 | 1528 | if (adj == size) { |
| 1197 | 4 | throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); |
| 1198 | } | |
| 1199 | 1524 | final F ins = filter.filter(obj); |
| 1200 | 1524 | if (ins != null) { |
| 1201 | 1449 | final F oldc = filter.filter(ContentList.this.set(adj, ins)); |
| 1202 | // optimize the backing.... | |
| 1203 | 1421 | xdata = getDataModCount(); |
| 1204 | 1421 | return oldc; |
| 1205 | } | |
| 1206 | 75 | throw new IllegalAddException("Filter won't allow index " + |
| 1207 | index + " to be set to " + | |
| 1208 | (obj.getClass()).getName()); | |
| 1209 | } | |
| 1210 | ||
| 1211 | /** | |
| 1212 | * Return the number of items in this list | |
| 1213 | * | |
| 1214 | * @return The number of items in this list. | |
| 1215 | */ | |
| 1216 | @Override | |
| 1217 | public int size() { | |
| 1218 | 11712 | resync(-1); |
| 1219 | 11712 | return backingsize; |
| 1220 | } | |
| 1221 | ||
| 1222 | /** | |
| 1223 | * Unlike the Arrays.binarySearch, this method never expects an | |
| 1224 | * "already exists" condition, we only ever add, thus there will never | |
| 1225 | * be a negative insertion-point. | |
| 1226 | * @param indexes THe pointers to search within | |
| 1227 | * @param len The number of pointers to search within | |
| 1228 | * @param val The pointer we are checking for. | |
| 1229 | * @param comp The Comparator to compare with | |
| 1230 | * @return the insertion point. | |
| 1231 | */ | |
| 1232 | @SuppressWarnings("unchecked") | |
| 1233 | private final int fbinarySearch(final int[] indexes, final int len, | |
| 1234 | final int val, final Comparator<? super F> comp) { | |
| 1235 | 41 | int left = 0, mid = 0, right = len - 1, cmp = 0; |
| 1236 | 41 | final F base = (F)elementData[backingpos[val]]; |
| 1237 | 84 | while (left <= right) { |
| 1238 | 50 | mid = (left + right) >>> 1; |
| 1239 | 50 | cmp = comp.compare(base, (F)elementData[indexes[mid]]); |
| 1240 | 50 | if (cmp == 0) { |
| 1241 | 11 | while (cmp == 0 && mid < right && comp.compare( |
| 1242 | base, (F)elementData[indexes[mid + 1]]) == 0) { | |
| 1243 | 4 | mid++; |
| 1244 | } | |
| 1245 | 7 | return mid + 1; |
| 1246 | 43 | } else if (cmp < 0) { |
| 1247 | 24 | right = mid - 1; |
| 1248 | } else { | |
| 1249 | 19 | left = mid + 1; |
| 1250 | } | |
| 1251 | } | |
| 1252 | 34 | return left; |
| 1253 | } | |
| 1254 | ||
| 1255 | ||
| 1256 | /** | |
| 1257 | * Sorts this list using the supplied Comparator to compare elements. | |
| 1258 | * | |
| 1259 | * @param comp - the Comparator used to compare list elements. A null value indicates that the elements' natural ordering should be used | |
| 1260 | * @Since 2.0.6 | |
| 1261 | */ | |
| 1262 | //Not till Java8 @Override | |
| 1263 | public final void sort(final Comparator<? super F> comp) { | |
| 1264 | // this size() forces a full scan/update of the list. | |
| 1265 | 10 | if (comp == null) { |
| 1266 | // sort by the 'natural order', which, there is none. | |
| 1267 | // options, throw exception, or let the current-order represent the natural order. | |
| 1268 | // do nothing is the better alternative. | |
| 1269 | 0 | return; |
| 1270 | } | |
| 1271 | 10 | final int sz = size(); |
| 1272 | 10 | final int[] indexes = new int[sz]; |
| 1273 | 51 | for (int i = 0 ; i < sz; i++) { |
| 1274 | 41 | final int ip = fbinarySearch(indexes, i, i, comp); |
| 1275 | 41 | if (ip < i) { |
| 1276 | 20 | System.arraycopy(indexes, ip, indexes, ip+1, i - ip); |
| 1277 | } | |
| 1278 | 41 | indexes[ip] = backingpos[i]; |
| 1279 | } | |
| 1280 | 10 | sortInPlace(indexes); |
| 1281 | 10 | } |
| 1282 | ||
| 1283 | } | |
| 1284 | ||
| 1285 | /* * * * * * * * * * * * * FilterListIterator * * * * * * * * * * * */ | |
| 1286 | /* * * * * * * * * * * * * FilterListIterator * * * * * * * * * * * */ | |
| 1287 | ||
| 1288 | 116221 | final class FilterListIterator<F extends Content> implements ListIterator<F> { |
| 1289 | ||
| 1290 | /** The Filter that applies */ | |
| 1291 | private final FilterList<F> filterlist; | |
| 1292 | ||
| 1293 | /** Whether this iterator is in forward or reverse. */ | |
| 1294 | 18303 | private boolean forward = false; |
| 1295 | /** Whether a call to remove() is valid */ | |
| 1296 | 18303 | private boolean canremove = false; |
| 1297 | /** Whether a call to set() is valid */ | |
| 1298 | 18303 | private boolean canset = false; |
| 1299 | ||
| 1300 | /** Expected modCount in our backing list */ | |
| 1301 | 18303 | private int expectedmod = -1; |
| 1302 | ||
| 1303 | 18303 | private int cursor = -1; |
| 1304 | ||
| 1305 | /** | |
| 1306 | * Default constructor | |
| 1307 | * | |
| 1308 | * @param flist | |
| 1309 | * The FilterList over which we will iterate. | |
| 1310 | * @param start | |
| 1311 | * where in the FilterList to start iteration. | |
| 1312 | */ | |
| 1313 | 18303 | FilterListIterator(final FilterList<F> flist, final int start) { |
| 1314 | 18303 | filterlist = flist; |
| 1315 | 18303 | expectedmod = getModCount(); |
| 1316 | // always start list iterators in backward mode .... | |
| 1317 | // it makes sense... really. | |
| 1318 | 18303 | forward = false; |
| 1319 | ||
| 1320 | 18303 | if (start < 0) { |
| 1321 | 1 | throw new IndexOutOfBoundsException("Index: " + start + " Size: " + filterlist.size()); |
| 1322 | } | |
| 1323 | ||
| 1324 | 18302 | final int adj = filterlist.resync(start); |
| 1325 | ||
| 1326 | 18302 | if (adj == size && start > filterlist.size()) { |
| 1327 | // the start point is after the end of the list. | |
| 1328 | // it is only allowed to be the same as size(), no larger. | |
| 1329 | 1 | throw new IndexOutOfBoundsException("Index: " + start + " Size: " + filterlist.size()); |
| 1330 | } | |
| 1331 | ||
| 1332 | 18301 | cursor = start; |
| 1333 | 18301 | } |
| 1334 | ||
| 1335 | private void checkConcurrent() { | |
| 1336 | 145868 | if (expectedmod != getModCount()) { |
| 1337 | 21 | throw new ConcurrentModificationException("The ContentList " + |
| 1338 | "supporting the FilterList this iterator is " + | |
| 1339 | "processing has been modified by something other " + | |
| 1340 | "than this Iterator."); | |
| 1341 | } | |
| 1342 | 145847 | } |
| 1343 | ||
| 1344 | /** | |
| 1345 | * Returns <code>true</code> if this list iterator has a next element. | |
| 1346 | */ | |
| 1347 | @Override | |
| 1348 | public boolean hasNext() { | |
| 1349 | 69570 | return filterlist.resync(forward ? cursor + 1 : cursor) < size; |
| 1350 | } | |
| 1351 | ||
| 1352 | /** | |
| 1353 | * Returns <code>true</code> if this list iterator has more elements | |
| 1354 | * when traversing the list in the reverse direction. | |
| 1355 | */ | |
| 1356 | @Override | |
| 1357 | public boolean hasPrevious() { | |
| 1358 | 28021 | return (forward ? cursor : cursor - 1) >= 0; |
| 1359 | } | |
| 1360 | ||
| 1361 | /** | |
| 1362 | * Returns the index of the element that would be returned by a | |
| 1363 | * subsequent call to <code>next</code>. | |
| 1364 | */ | |
| 1365 | @Override | |
| 1366 | public int nextIndex() { | |
| 1367 | 78375 | return forward ? cursor + 1 : cursor; |
| 1368 | } | |
| 1369 | ||
| 1370 | /** | |
| 1371 | * Returns the index of the element that would be returned by a | |
| 1372 | * subsequent call to <code>previous</code>. (Returns -1 if the list | |
| 1373 | * iterator is at the beginning of the list.) | |
| 1374 | */ | |
| 1375 | @Override | |
| 1376 | public int previousIndex() { | |
| 1377 | 70105 | return forward ? cursor : cursor - 1; |
| 1378 | } | |
| 1379 | ||
| 1380 | /** | |
| 1381 | * Returns the next element in the list. | |
| 1382 | */ | |
| 1383 | @Override | |
| 1384 | public F next() { | |
| 1385 | 64371 | checkConcurrent(); |
| 1386 | 64365 | final int next = forward ? cursor + 1 : cursor; |
| 1387 | ||
| 1388 | 64365 | if (filterlist.resync(next) >= size) { |
| 1389 | 4419 | throw new NoSuchElementException("next() is beyond the end of the Iterator"); |
| 1390 | } | |
| 1391 | ||
| 1392 | 59946 | cursor = next; |
| 1393 | 59946 | forward = true; |
| 1394 | 59946 | canremove = true; |
| 1395 | 59946 | canset = true; |
| 1396 | 59946 | return filterlist.get(cursor); |
| 1397 | } | |
| 1398 | ||
| 1399 | /** | |
| 1400 | * Returns the previous element in the list. | |
| 1401 | */ | |
| 1402 | @Override | |
| 1403 | public F previous() { | |
| 1404 | 35138 | checkConcurrent(); |
| 1405 | 35135 | final int prev = forward ? cursor : cursor - 1; |
| 1406 | ||
| 1407 | 35135 | if (prev < 0) { |
| 1408 | 2380 | throw new NoSuchElementException("previous() is beyond the beginning of the Iterator"); |
| 1409 | } | |
| 1410 | ||
| 1411 | 32755 | cursor = prev; |
| 1412 | 32755 | forward = false; |
| 1413 | 32755 | canremove = true; |
| 1414 | 32755 | canset = true; |
| 1415 | 32755 | return filterlist.get(cursor); |
| 1416 | } | |
| 1417 | ||
| 1418 | /** | |
| 1419 | * Inserts the specified element into the list . | |
| 1420 | */ | |
| 1421 | @Override | |
| 1422 | public void add(final Content obj) { | |
| 1423 | 14789 | checkConcurrent(); |
| 1424 | // always add before what would normally be returned by next(); | |
| 1425 | 14786 | final int next = forward ? cursor + 1 : cursor; |
| 1426 | ||
| 1427 | 14786 | filterlist.add(next, obj); |
| 1428 | ||
| 1429 | 14735 | expectedmod = getModCount(); |
| 1430 | ||
| 1431 | 14735 | canremove = canset = false; |
| 1432 | ||
| 1433 | // a call to next() should be unaffected, so, whatever was going to | |
| 1434 | // be next will still be next, remember, what was going to be next | |
| 1435 | // has been shifted 'right' by our insert. | |
| 1436 | // we ensure this by setting the cursor to next(), and making it | |
| 1437 | // forward | |
| 1438 | 14735 | cursor = next; |
| 1439 | 14735 | forward = true; |
| 1440 | 14735 | } |
| 1441 | ||
| 1442 | /** | |
| 1443 | * Removes from the list the last element that was returned by the last | |
| 1444 | * call to <code>next</code> or <code>previous</code>. | |
| 1445 | */ | |
| 1446 | @Override | |
| 1447 | public void remove() { | |
| 1448 | 29647 | checkConcurrent(); |
| 1449 | 29641 | if (!canremove) |
| 1450 | 13851 | throw new IllegalStateException("Can not remove an " |
| 1451 | + "element unless either next() or previous() has been called " | |
| 1452 | + "since the last remove()"); | |
| 1453 | // we are removing the last entry returned by either next() or | |
| 1454 | // previous(). | |
| 1455 | // the idea is to remove it, and pretend that we used to be at the | |
| 1456 | // entry that happened *after* the removed entry. | |
| 1457 | // so, get what would be the next entry (set at tmpcursor). | |
| 1458 | // so call nextIndex to set tmpcursor to what would come after. | |
| 1459 | 15790 | filterlist.remove(cursor); |
| 1460 | 15790 | forward = false; |
| 1461 | 15790 | expectedmod = getModCount(); |
| 1462 | ||
| 1463 | 15790 | canremove = false; |
| 1464 | 15790 | canset = false; |
| 1465 | 15790 | } |
| 1466 | ||
| 1467 | /** | |
| 1468 | * Replaces the last element returned by <code>next</code> or | |
| 1469 | * <code>previous</code> with the specified element. | |
| 1470 | */ | |
| 1471 | @Override | |
| 1472 | public void set(final F obj) { | |
| 1473 | 1923 | checkConcurrent(); |
| 1474 | 1920 | if (!canset) { |
| 1475 | 452 | throw new IllegalStateException("Can not set an element " |
| 1476 | + "unless either next() or previous() has been called since the " | |
| 1477 | + "last remove() or set()"); | |
| 1478 | } | |
| 1479 | ||
| 1480 | 1468 | filterlist.set(cursor, obj); |
| 1481 | 1417 | expectedmod = getModCount(); |
| 1482 | ||
| 1483 | 1417 | } |
| 1484 | ||
| 1485 | } | |
| 1486 | ||
| 1487 | } |