1 /*
   2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import org.testng.annotations.Test;
  25 
  26 import java.lang.reflect.Constructor;
  27 import java.util.ArrayList;
  28 import java.util.Arrays;
  29 import java.util.Collection;
  30 import java.util.Collections;
  31 import java.util.Iterator;
  32 import java.util.List;
  33 import java.util.ListIterator;
  34 import java.util.NoSuchElementException;
  35 
  36 import static org.testng.Assert.assertEquals;
  37 import static org.testng.Assert.assertFalse;
  38 import static org.testng.Assert.assertTrue;
  39 import static org.testng.Assert.fail;
  40 
  41 /**
  42  * @test
  43  * @run testng IteratorDefaults
  44  * @summary test extension methods on Iterator
  45  */
  46 @Test
  47 public class IteratorDefaults {
  48 
  49     private static interface Callback {
  50         void call(List<Integer> list);
  51     }
  52 
  53     // call the callback for each recursive subList
  54     private void trimmedSubList(final List<Integer> list, final Callback callback) {
  55         int size = list.size();
  56         if (size > 1) {
  57             // trim 1 element from both ends
  58             final List<Integer> subList = list.subList(1, size - 1);
  59             callback.call(subList);
  60             trimmedSubList(subList, callback);
  61         }
  62     }
  63 
  64     public void testRemoveUnsupported() {
  65         final Iterator iterator = new Iterator() {
  66             @Override
  67             public boolean hasNext() {
  68                 return false;
  69             }
  70             @Override
  71             public Object next() {
  72                 return null;
  73             }
  74         };
  75 
  76         try {
  77             iterator.remove();
  78             fail("expected UnsupportedOperationException from remove not thrown");
  79         } catch (UnsupportedOperationException ignore) {
  80         }
  81     }
  82 
  83     public void testRemoveOverride() {
  84         final IteratorWithRemove iterator = new IteratorWithRemove();
  85         iterator.remove();
  86         assertTrue(iterator.removed);
  87     }
  88 
  89     public void testForEach() throws Exception {
  90         final Integer[] data = new Integer[1000];
  91         for (int i=0; i < data.length; i++) {
  92             data[i] = i;
  93         }
  94         final List<Integer> source = Arrays.asList(data);
  95 
  96         final String[] iterableCollectionClasses = {
  97                 "java.util.ArrayDeque",
  98                 "java.util.ArrayList",
  99                 "java.util.HashSet",
 100                 "java.util.LinkedHashSet",
 101                 "java.util.LinkedList",
 102                 "java.util.PriorityQueue",
 103                 "java.util.TreeSet",
 104                 "java.util.Vector",
 105                 "java.util.concurrent.ConcurrentLinkedDeque",
 106                 "java.util.concurrent.ConcurrentLinkedQueue",
 107                 "java.util.concurrent.ConcurrentSkipListSet",
 108                 "java.util.concurrent.CopyOnWriteArrayList",
 109                 "java.util.concurrent.CopyOnWriteArraySet",
 110                 "java.util.concurrent.LinkedBlockingDeque",
 111                 "java.util.concurrent.LinkedBlockingQueue",
 112                 "java.util.concurrent.LinkedTransferQueue",
 113                 "java.util.concurrent.PriorityBlockingQueue"
 114         };
 115 
 116         for (final String iterableClass : iterableCollectionClasses) {
 117             final Iterable<Integer> iterable =
 118                     (Iterable<Integer>) Class.forName(iterableClass).newInstance();
 119             ((Collection<Integer>) iterable).addAll(source);
 120             final Iterator<Integer> iterator = iterable.iterator();
 121             final List<Integer> target = new ArrayList<>(source.size());
 122             iterator.forEachRemaining(target::add);
 123             if ("java.util.HashSet".equals(iterableClass)) {
 124                 target.sort((x, y) -> x - y);
 125                 assertEquals(target, source);
 126             } else {
 127                 assertEquals(target, source);
 128             }
 129 
 130             // verify that for an iterator that has been advanced via next(),
 131             // forEach starts from the current location, not zero
 132             final int OFFSET = 5;
 133             final List<Integer> reference2 = new ArrayList<>(source).subList(OFFSET, source.size());
 134             final List<Integer> removed2 = new ArrayList<>(OFFSET);
 135             final Iterator<Integer> iterator2 = iterable.iterator();
 136             for (int i=0; i < OFFSET; i++) {
 137                 // advance the iterator by OFFSET, saving iterated elements
 138                 removed2.add(iterator2.next());
 139             }
 140             final List<Integer> target2 = new ArrayList<>(reference2.size());
 141             iterator2.forEachRemaining(target2::add);
 142             if ("java.util.HashSet".equals(iterableClass)) {
 143                 assertEquals(target2.size(), reference2.size());
 144                 target2.addAll(removed2);
 145                 target2.sort((x, y) -> x - y);
 146                 assertEquals(target2, source);
 147                 assertEquals(target2.subList(OFFSET, source.size()), reference2);
 148             } else {
 149                 assertEquals(target2, reference2);
 150             }
 151         }
 152     }
 153 
 154     public void testForEachSubList() throws Exception {
 155         final Integer[] data = new Integer[100];
 156         for (int i = 0; i < data.length; i++) {
 157             data[i] = i;
 158         }
 159         final List<Integer> source = Arrays.asList(data);
 160         final String[] listClasses = {
 161                 "java.util.ArrayList",
 162                 "java.util.LinkedList",
 163                 "java.util.Vector",
 164                 "java.util.concurrent.CopyOnWriteArrayList"
 165         };
 166         for (final String listClass : listClasses) {
 167             final List<Integer> list =
 168                     (List<Integer>) Class.forName(listClass).newInstance();
 169             list.addAll(source);
 170             trimmedSubList(list, new Callback() {
 171                 @Override
 172                 public void call(final List<Integer> list) {
 173                     if (list.size() < 1) {
 174                         return;
 175                     }
 176                     final List<Integer> target = new ArrayList<>(list.size());
 177                     final ListIterator<Integer> iterator = list.listIterator();
 178                     assertTrue(iterator.hasNext());
 179                     assertFalse(iterator.hasPrevious());
 180                     assertEquals(iterator.nextIndex(), 0);
 181                     assertEquals(iterator.previousIndex(), -1);
 182 
 183                     iterator.forEachRemaining(target::add);
 184                     assertEquals(target, list);
 185 
 186                     assertFalse(iterator.hasNext());
 187                     assertTrue(iterator.hasPrevious());
 188                     assertEquals(iterator.nextIndex(), list.size());
 189                     assertEquals(iterator.previousIndex(), list.size() - 1);
 190 
 191                     try {
 192                         iterator.next();
 193                         fail(listClass + " iterator advanced beyond end");
 194                     } catch (NoSuchElementException ignore) {
 195                     }
 196                 }
 197             });
 198         }
 199     }
 200 
 201     public void testOptimizedForEach() throws Exception {
 202         final Integer[] data = new Integer[1000 * 1000];
 203         for (int i=0; i < data.length; i++) {
 204             data[i] = i;
 205         }
 206         final List<Integer> source = Arrays.asList(data);
 207 
 208         final String[] listClasses = {
 209                 "java.util.ArrayList",
 210                 "java.util.LinkedList",
 211                 "java.util.Vector",
 212                 "java.util.concurrent.CopyOnWriteArrayList"
 213         };
 214 
 215         final int OFFSET = 3;
 216         final List<Integer> target = new ArrayList<>(source);
 217         for (final String listClass : listClasses) {
 218             final List<Integer> list =
 219                     (List<Integer>) Class.forName(listClass).newInstance();
 220             list.addAll(source);
 221             final ListIterator<Integer> iterator = list.listIterator();
 222             assertFalse(iterator.hasPrevious());
 223             for (int i=0; i < OFFSET; i++) {
 224                 iterator.next();
 225             }
 226             assertTrue(iterator.hasNext());
 227             assertTrue(iterator.hasPrevious());
 228             assertEquals(iterator.nextIndex(), OFFSET);
 229             assertEquals(iterator.previousIndex(), OFFSET - 1);
 230 
 231             iterator.forEachRemaining(e -> {
 232                 target.set(e, e + 1);
 233             });
 234             for (int i=OFFSET; i < data.length; i++) {
 235                 assertEquals(target.get(i).intValue(), source.get(i)+1);
 236             }
 237 
 238             assertFalse(iterator.hasNext());
 239             assertTrue(iterator.hasPrevious());
 240             assertEquals(iterator.nextIndex(), data.length);
 241             assertEquals(iterator.previousIndex(), data.length - 1);
 242 
 243             // CopyOnWriteArrayList.listIterator().remove() is unsupported
 244             if (!"java.util.concurrent.CopyOnWriteArrayList".equals(listClass)) {
 245                 for (int i = data.length - 1; i >= 0; i--) {
 246                     iterator.remove(); // must not throw
 247                     if (i > 0) {
 248                         iterator.previous();
 249                     }
 250                 }
 251                 assertTrue(list.isEmpty());
 252             }
 253 
 254             try {
 255                 iterator.next();
 256                 fail(listClass + " iterator advanced beyond end");
 257             } catch (NoSuchElementException ignore) {
 258             }
 259         }
 260     }
 261 
 262     @Test(enabled = false)
 263     public void compareForEachPerformance() throws Exception {
 264         final Integer[] data = new Integer[1000 * 100];
 265         for (int i=0; i < data.length; i++) {
 266             data[i] = i;
 267         }
 268         final List<Integer> source = Arrays.asList(data);
 269 
 270         final String[] iterableCollectionClasses = {
 271                 "java.util.ArrayList", // warmup, results discarded
 272                 "java.util.ArrayDeque",
 273                 "java.util.ArrayList",
 274                 "java.util.HashSet",
 275                 "java.util.LinkedHashSet",
 276                 "java.util.LinkedList",
 277                 "java.util.PriorityQueue",
 278                 "java.util.TreeSet",
 279                 "java.util.Vector",
 280                 "java.util.concurrent.ConcurrentLinkedDeque",
 281                 "java.util.concurrent.ConcurrentLinkedQueue",
 282                 "java.util.concurrent.ConcurrentSkipListSet",
 283                 "java.util.concurrent.CopyOnWriteArrayList",
 284                 "java.util.concurrent.CopyOnWriteArraySet",
 285                 "java.util.concurrent.LinkedBlockingDeque",
 286                 "java.util.concurrent.LinkedBlockingQueue",
 287                 "java.util.concurrent.LinkedTransferQueue",
 288                 "java.util.concurrent.PriorityBlockingQueue"
 289         };
 290 
 291         boolean warmup = true;
 292         final int ITERATIONS = 10;
 293         final Integer[] target = new Integer[source.size()];
 294         for (final String iterableClass : iterableCollectionClasses) {
 295             final Class<? extends Collection<Integer>> type =
 296                     (Class<? extends Collection<Integer>>) Class.forName(iterableClass);
 297             final Constructor<? extends Collection<Integer>> copyConstructor =
 298                     type.getConstructor(Collection.class);
 299             final Iterable<Integer> iterable = copyConstructor.newInstance(source);
 300             final Iterable<Integer> reference =
 301                     Collections.unmodifiableCollection((Collection<Integer>) iterable);
 302 
 303             for (int i=0; i < ITERATIONS; i++) {
 304                 final Iterator<Integer> iterator = reference.iterator();
 305                 final long forEachStart = System.nanoTime();
 306                 iterator.forEachRemaining(x -> {target[x.intValue()] = x;});
 307                 final long forEachEnd = System.nanoTime();
 308 
 309                 final Iterator<Integer> iterator2 = reference.iterator();
 310                 Integer x;
 311                 final long iteratorStart = System.nanoTime();
 312                 while (iterator2.hasNext()) {
 313                     x = iterator2.next();
 314                     target[x.intValue()] = x;
 315                 }
 316                 final long iteratorEnd = System.nanoTime();
 317 
 318                 if (warmup) { continue; } // warmup, discard results
 319                 final long forEachTime = forEachEnd - forEachStart;
 320                 final long iteratorTime = iteratorEnd - iteratorStart;
 321                 final long speedup = iteratorTime - forEachTime;
 322                 System.out.print(iterableClass);
 323                 System.out.print(" iterator: ");
 324                 System.out.print(iteratorTime);
 325                 System.out.print(", forEach: ");
 326                 System.out.print(forEachTime);
 327                 System.out.print(", speedup: ");
 328                 System.out.print(speedup);
 329                 System.out.print(" (");
 330                 System.out.print((speedup * 100) / iteratorTime);
 331                 System.out.print("%)\n");
 332             }
 333             if (warmup) { warmup = false; }
 334             System.out.println();
 335         }
 336     }
 337 
 338     @Test(enabled = false)
 339     public void compareSubListForEachPerformance() throws Exception {
 340         final Integer[] data = new Integer[1000 * 100];
 341         for (int i = 0; i < data.length; i++) {
 342             data[i] = i;
 343         }
 344         final List<Integer> source = Arrays.asList(data);
 345 
 346         final String[] listClasses = {
 347                 "java.util.ArrayList", // warmup, results discarded
 348                 "java.util.ArrayList",
 349                 "java.util.LinkedList",
 350                 "java.util.Vector",
 351                 "java.util.concurrent.CopyOnWriteArrayList"
 352         };
 353 
 354         boolean warmup = true;
 355         final int ITERATIONS = 10;
 356         final Integer[] target = new Integer[source.size()];
 357         for (final String listClass : listClasses) {
 358             final Class<? extends List<Integer >> type =
 359                     (Class<? extends List<Integer>>) Class.forName(listClass);
 360             final Constructor<? extends List<Integer >> copyConstructor =
 361                     type.getConstructor(Collection.class);
 362             final List<Integer> iterable = copyConstructor.newInstance(source);
 363             final List<Integer> reference = Collections.unmodifiableList(iterable);
 364 
 365             for (int i = 0; i < ITERATIONS; i++) {
 366                 final Iterator<Integer> iterator = reference.subList(42, reference.size() - 37).iterator();
 367                 final long forEachStart = System.nanoTime();
 368                 iterator.forEachRemaining(x -> {
 369                     target[x.intValue()] = x;
 370                 });
 371                 final long forEachEnd = System.nanoTime();
 372 
 373                 final Iterator<Integer> iterator2 = reference.iterator();
 374                 Integer x;
 375                 final long iteratorStart = System.nanoTime();
 376                 while (iterator2.hasNext()) {
 377                     x = iterator2.next();
 378                     target[x.intValue()] = x;
 379                 }
 380                 final long iteratorEnd = System.nanoTime();
 381 
 382                 if (warmup) { continue; } // warmup, discard results
 383                 final long forEachTime = forEachEnd - forEachStart;
 384                 final long iteratorTime = iteratorEnd - iteratorStart;
 385                 final long speedup = iteratorTime - forEachTime;
 386                 System.out.print(listClass);
 387                 System.out.print(" iterator: ");
 388                 System.out.print(iteratorTime);
 389                 System.out.print(", forEach: ");
 390                 System.out.print(forEachTime);
 391                 System.out.print(", speedup: ");
 392                 System.out.print(speedup);
 393                 System.out.print(" (");
 394                 System.out.print((speedup * 100) / iteratorTime);
 395                 System.out.print("%)\n");
 396             }
 397             if (warmup) { warmup = false; }
 398             System.out.println();
 399         }
 400     }
 401 
 402     static class IteratorWithRemove implements Iterator {
 403 
 404         public boolean removed;
 405 
 406         IteratorWithRemove() {
 407             removed = false;
 408         }
 409 
 410         @Override
 411         public boolean hasNext() {
 412             return false;
 413         }
 414 
 415         @Override
 416         public Object next() {
 417             return null;
 418         }
 419 
 420         @Override
 421         public void remove() {
 422             removed = true;
 423         }
 424     }
 425 }