1 /*
   2  * Copyright (c) 2013, 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.DataProvider;
  25 import org.testng.annotations.Test;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Arrays;
  29 import java.util.Collection;
  30 import java.util.Collections;
  31 import java.util.ConcurrentModificationException;
  32 import java.util.HashMap;
  33 import java.util.HashSet;
  34 import java.util.LinkedHashMap;
  35 import java.util.LinkedHashSet;
  36 import java.util.LinkedList;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.PriorityQueue;
  40 import java.util.Set;
  41 import java.util.Spliterator;
  42 import java.util.Stack;
  43 import java.util.TreeMap;
  44 import java.util.TreeSet;
  45 import java.util.Vector;
  46 import java.util.WeakHashMap;
  47 import java.util.function.Consumer;
  48 import java.util.function.Function;
  49 import java.util.function.Supplier;
  50 
  51 import static org.testng.Assert.*;
  52 
  53 /**
  54  * @test
  55  * @summary Spliterator last-binding and fail-fast tests
  56  * @run testng SpliteratorLateBindingFailFastTest
  57  */
  58 
  59 @Test
  60 public class SpliteratorLateBindingFailFastTest {
  61 
  62     private interface Source<T> {
  63         Collection<T> asCollection();
  64         void update();
  65     }
  66 
  67     private static class SpliteratorDataBuilder<T> {
  68         final List<Object[]> data;
  69 
  70         final T newValue;
  71 
  72         final List<T> exp;
  73 
  74         final Map<T, T> mExp;
  75 
  76         SpliteratorDataBuilder(List<Object[]> data, T newValue, List<T> exp) {
  77             this.data = data;
  78             this.newValue = newValue;
  79             this.exp = exp;
  80             this.mExp = createMap(exp);
  81         }
  82 
  83         Map<T, T> createMap(List<T> l) {
  84             Map<T, T> m = new LinkedHashMap<>();
  85             for (T t : l) {
  86                 m.put(t, t);
  87             }
  88             return m;
  89         }
  90 
  91         void add(String description, Supplier<Source<?>> s) {
  92             description = joiner(description).toString();
  93             data.add(new Object[]{description, s});
  94         }
  95 
  96         void addCollection(Function<Collection<T>, ? extends Collection<T>> f) {
  97             class CollectionSource implements Source<T> {
  98                 final Collection<T> c = f.apply(exp);
  99 
 100                 final Consumer<Collection<T>> updater;
 101 
 102                 CollectionSource(Consumer<Collection<T>> updater) {
 103                     this.updater = updater;
 104                 }
 105 
 106                 @Override
 107                 public Collection<T> asCollection() {
 108                     return c;
 109                 }
 110 
 111                 @Override
 112                 public void update() {
 113                     updater.accept(c);
 114                 }
 115             }
 116 
 117             String description = "new " + f.apply(Collections.<T>emptyList()).getClass().getName() + ".spliterator() ";
 118             add(description + "ADD", () -> new CollectionSource(c -> c.add(newValue)));
 119             add(description + "REMOVE", () -> new CollectionSource(c -> c.remove(c.iterator().next())));
 120         }
 121 
 122         void addList(Function<Collection<T>, ? extends List<T>> l) {
 123             // @@@ If collection is instance of List then add sub-list tests
 124             addCollection(l);
 125         }
 126 
 127         void addMap(Function<Map<T, T>, ? extends Map<T, T>> mapConstructor) {
 128             class MapSource<U> implements Source<U> {
 129                 final Map<T, T> m = mapConstructor.apply(mExp);
 130 
 131                 final Collection<U> c;
 132 
 133                 final Consumer<Map<T, T>> updater;
 134 
 135                 MapSource(Function<Map<T, T>, Collection<U>> f, Consumer<Map<T, T>> updater) {
 136                     this.c = f.apply(m);
 137                     this.updater = updater;
 138                 }
 139 
 140                 @Override
 141                 public Collection<U> asCollection() {
 142                     return c;
 143                 }
 144 
 145                 @Override
 146                 public void update() {
 147                     updater.accept(m);
 148                 }
 149             }
 150 
 151             Map<String, Consumer<Map<T, T>>> actions = new HashMap<>();
 152             actions.put("ADD", m -> m.put(newValue, newValue));
 153             actions.put("REMOVE", m -> m.remove(m.keySet().iterator().next()));
 154 
 155             String description = "new " + mapConstructor.apply(Collections.<T, T>emptyMap()).getClass().getName();
 156             for (Map.Entry<String, Consumer<Map<T, T>>> e : actions.entrySet()) {
 157                 add(description + ".keySet().spliterator() " + e.getKey(),
 158                     () -> new MapSource<T>(m -> m.keySet(), e.getValue()));
 159                 add(description + ".values().spliterator() " + e.getKey(),
 160                     () -> new MapSource<T>(m -> m.values(), e.getValue()));
 161                 add(description + ".entrySet().spliterator() " + e.getKey(),
 162                     () -> new MapSource<Map.Entry<T, T>>(m -> m.entrySet(), e.getValue()));
 163             }
 164         }
 165 
 166         StringBuilder joiner(String description) {
 167             return new StringBuilder(description).
 168                     append(" {").
 169                     append("size=").append(exp.size()).
 170                     append("}");
 171         }
 172     }
 173 
 174     static Object[][] spliteratorDataProvider;
 175 
 176     @DataProvider(name = "Source")
 177     public static Object[][] spliteratorDataProvider() {
 178         if (spliteratorDataProvider != null) {
 179             return spliteratorDataProvider;
 180         }
 181 
 182         List<Object[]> data = new ArrayList<>();
 183         SpliteratorDataBuilder<Integer> db = new SpliteratorDataBuilder<>(data, 5, Arrays.asList(1, 2, 3, 4));
 184 
 185         // Collections
 186 
 187         db.addList(ArrayList::new);
 188 
 189         db.addList(LinkedList::new);
 190 
 191         db.addList(Vector::new);
 192 
 193 
 194         db.addCollection(HashSet::new);
 195 
 196         db.addCollection(LinkedHashSet::new);
 197 
 198         db.addCollection(TreeSet::new);
 199 
 200 
 201         db.addCollection(c -> { Stack<Integer> s = new Stack<>(); s.addAll(c); return s;});
 202 
 203         db.addCollection(PriorityQueue::new);
 204 
 205         // ArrayDeque fails some tests since its fail-fast support is weaker
 206         // than other collections and limited to detecting most, but not all,
 207         // removals.  It probably requires its own test since it is difficult
 208         // to abstract out the conditions under which it fails-fast.
 209 //        db.addCollection(ArrayDeque::new);
 210 
 211         // Maps
 212 
 213         db.addMap(HashMap::new);
 214 
 215         db.addMap(LinkedHashMap::new);
 216 
 217         // This fails when run through jtreg but passes when run through
 218         // ant
 219 //        db.addMap(IdentityHashMap::new);
 220 
 221         db.addMap(WeakHashMap::new);
 222 
 223         // @@@  Descending maps etc
 224         db.addMap(TreeMap::new);
 225 
 226         return spliteratorDataProvider = data.toArray(new Object[0][]);
 227     }
 228 
 229     @Test(dataProvider = "Source")
 230     public <T> void lateBindingTestWithForEach(String description, Supplier<Source<T>> ss) {
 231         Source<T> source = ss.get();
 232         Collection<T> c = source.asCollection();
 233         Spliterator<T> s = c.spliterator();
 234 
 235         source.update();
 236 
 237         Set<T> r = new HashSet<>();
 238         s.forEachRemaining(r::add);
 239 
 240         assertEquals(r, new HashSet<>(c));
 241     }
 242 
 243     @Test(dataProvider = "Source")
 244     public <T> void lateBindingTestWithTryAdvance(String description, Supplier<Source<T>> ss) {
 245         Source<T> source = ss.get();
 246         Collection<T> c = source.asCollection();
 247         Spliterator<T> s = c.spliterator();
 248 
 249         source.update();
 250 
 251         Set<T> r = new HashSet<>();
 252         while (s.tryAdvance(r::add)) { }
 253 
 254         assertEquals(r, new HashSet<>(c));
 255     }
 256 
 257     @Test(dataProvider = "Source")
 258     public <T> void lateBindingTestWithCharacteritics(String description, Supplier<Source<T>> ss) {
 259         Source<T> source = ss.get();
 260         Collection<T> c = source.asCollection();
 261         Spliterator<T> s = c.spliterator();
 262         s.characteristics();
 263 
 264         Set<T> r = new HashSet<>();
 265         s.forEachRemaining(r::add);
 266 
 267         assertEquals(r, new HashSet<>(c));
 268     }
 269 
 270 
 271     @Test(dataProvider = "Source")
 272     public <T> void testFailFastTestWithTryAdvance(String description, Supplier<Source<T>> ss) {
 273         {
 274             Source<T> source = ss.get();
 275             Collection<T> c = source.asCollection();
 276             Spliterator<T> s = c.spliterator();
 277 
 278             s.tryAdvance(e -> {
 279             });
 280             source.update();
 281 
 282             executeAndCatch(() -> s.tryAdvance(e -> { }));
 283         }
 284 
 285         {
 286             Source<T> source = ss.get();
 287             Collection<T> c = source.asCollection();
 288             Spliterator<T> s = c.spliterator();
 289 
 290             s.tryAdvance(e -> {
 291             });
 292             source.update();
 293 
 294             executeAndCatch(() -> s.forEachRemaining(e -> {
 295             }));
 296         }
 297     }
 298 
 299     @Test(dataProvider = "Source")
 300     public <T> void testFailFastTestWithForEach(String description, Supplier<Source<T>> ss) {
 301         Source<T> source = ss.get();
 302         Collection<T> c = source.asCollection();
 303         Spliterator<T> s = c.spliterator();
 304 
 305         executeAndCatch(() -> s.forEachRemaining(e -> {
 306             source.update();
 307         }));
 308     }
 309 
 310     @Test(dataProvider = "Source")
 311     public <T> void testFailFastTestWithEstimateSize(String description, Supplier<Source<T>> ss) {
 312         {
 313             Source<T> source = ss.get();
 314             Collection<T> c = source.asCollection();
 315             Spliterator<T> s = c.spliterator();
 316 
 317             s.estimateSize();
 318             source.update();
 319 
 320             executeAndCatch(() -> s.tryAdvance(e -> { }));
 321         }
 322 
 323         {
 324             Source<T> source = ss.get();
 325             Collection<T> c = source.asCollection();
 326             Spliterator<T> s = c.spliterator();
 327 
 328             s.estimateSize();
 329             source.update();
 330 
 331             executeAndCatch(() -> s.forEachRemaining(e -> {
 332             }));
 333         }
 334     }
 335 
 336     private void executeAndCatch(Runnable r) {
 337         executeAndCatch(ConcurrentModificationException.class, r);
 338     }
 339 
 340     private void executeAndCatch(Class<? extends Exception> expected, Runnable r) {
 341         Exception caught = null;
 342         try {
 343             r.run();
 344         }
 345         catch (Exception e) {
 346             caught = e;
 347         }
 348 
 349         assertNotNull(caught,
 350                       String.format("No Exception was thrown, expected an Exception of %s to be thrown",
 351                                     expected.getName()));
 352         assertTrue(expected.isInstance(caught),
 353                    String.format("Exception thrown %s not an instance of %s",
 354                                  caught.getClass().getName(), expected.getName()));
 355     }
 356 
 357 }