001/*
002 * Copyright (C) 2008 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.collect.testing;
018
019import static com.google.common.collect.testing.Helpers.castOrCopyToList;
020import static com.google.common.collect.testing.Helpers.entryComparator;
021import static com.google.common.collect.testing.Helpers.equal;
022import static com.google.common.collect.testing.Helpers.mapEntry;
023import static java.util.Arrays.asList;
024import static java.util.Collections.sort;
025
026import com.google.common.annotations.GwtCompatible;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Comparator;
030import java.util.List;
031import java.util.Map;
032import java.util.Map.Entry;
033import java.util.Set;
034import java.util.SortedMap;
035import java.util.SortedSet;
036import org.jspecify.annotations.NullMarked;
037import org.checkerframework.checker.nullness.qual.Nullable;
038
039/**
040 * Derived suite generators, split out of the suite builders so that they are available to GWT.
041 *
042 * @author George van den Driessche
043 */
044@GwtCompatible
045@NullMarked
046public final class DerivedCollectionGenerators {
047  public static class MapEntrySetGenerator<K extends @Nullable Object, V extends @Nullable Object>
048      implements TestSetGenerator<Entry<K, V>>, DerivedGenerator {
049    private final OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator;
050
051    public MapEntrySetGenerator(
052        OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator) {
053      this.mapGenerator = mapGenerator;
054    }
055
056    @Override
057    public SampleElements<Entry<K, V>> samples() {
058      return mapGenerator.samples();
059    }
060
061    @Override
062    public Set<Entry<K, V>> create(Object... elements) {
063      return mapGenerator.create(elements).entrySet();
064    }
065
066    @Override
067    public Entry<K, V>[] createArray(int length) {
068      return mapGenerator.createArray(length);
069    }
070
071    @Override
072    public Iterable<Entry<K, V>> order(List<Entry<K, V>> insertionOrder) {
073      return mapGenerator.order(insertionOrder);
074    }
075
076    @Override
077    public OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> getInnerGenerator() {
078      return mapGenerator;
079    }
080  }
081
082  // TODO: investigate some API changes to SampleElements that would tidy up
083  // parts of the following classes.
084
085  static <K extends @Nullable Object, V extends @Nullable Object>
086      TestSetGenerator<K> keySetGenerator(
087          OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator) {
088    TestContainerGenerator<Map<K, V>, Entry<K, V>> generator = mapGenerator.getInnerGenerator();
089    if (generator instanceof TestSortedMapGenerator
090        && ((TestSortedMapGenerator<K, V>) generator).create().keySet() instanceof SortedSet) {
091      return new MapSortedKeySetGenerator<>(mapGenerator);
092    } else {
093      return new MapKeySetGenerator<>(mapGenerator);
094    }
095  }
096
097  public static class MapKeySetGenerator<K extends @Nullable Object, V extends @Nullable Object>
098      implements TestSetGenerator<K>, DerivedGenerator {
099    private final OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator;
100    private final SampleElements<K> samples;
101
102    public MapKeySetGenerator(OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator) {
103      this.mapGenerator = mapGenerator;
104      SampleElements<Entry<K, V>> mapSamples = this.mapGenerator.samples();
105      this.samples =
106          new SampleElements<>(
107              mapSamples.e0().getKey(),
108              mapSamples.e1().getKey(),
109              mapSamples.e2().getKey(),
110              mapSamples.e3().getKey(),
111              mapSamples.e4().getKey());
112    }
113
114    @Override
115    public SampleElements<K> samples() {
116      return samples;
117    }
118
119    @Override
120    public Set<K> create(Object... elements) {
121      @SuppressWarnings("unchecked")
122      K[] keysArray = (K[]) elements;
123
124      // Start with a suitably shaped collection of entries
125      Collection<Entry<K, V>> originalEntries = mapGenerator.getSampleElements(elements.length);
126
127      // Create a copy of that, with the desired value for each key
128      Collection<Entry<K, V>> entries = new ArrayList<>(elements.length);
129      int i = 0;
130      for (Entry<K, V> entry : originalEntries) {
131        entries.add(mapEntry(keysArray[i++], entry.getValue()));
132      }
133
134      return mapGenerator.create(entries.toArray()).keySet();
135    }
136
137    @Override
138    public K[] createArray(int length) {
139      // TODO: with appropriate refactoring of OneSizeGenerator, we can perhaps
140      // tidy this up and get rid of the casts here and in
141      // MapValueCollectionGenerator.
142
143      return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createKeyArray(length);
144    }
145
146    @Override
147    public Iterable<K> order(List<K> insertionOrder) {
148      V v = ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).samples().e0().getValue();
149      List<Entry<K, V>> entries = new ArrayList<>();
150      for (K element : insertionOrder) {
151        entries.add(mapEntry(element, v));
152      }
153
154      List<K> keys = new ArrayList<>();
155      for (Entry<K, V> entry : mapGenerator.order(entries)) {
156        keys.add(entry.getKey());
157      }
158      return keys;
159    }
160
161    @Override
162    public OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> getInnerGenerator() {
163      return mapGenerator;
164    }
165  }
166
167  public static class MapSortedKeySetGenerator<
168          K extends @Nullable Object, V extends @Nullable Object>
169      extends MapKeySetGenerator<K, V> implements TestSortedSetGenerator<K>, DerivedGenerator {
170    private final TestSortedMapGenerator<K, V> delegate;
171
172    public MapSortedKeySetGenerator(
173        OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator) {
174      super(mapGenerator);
175      this.delegate = (TestSortedMapGenerator<K, V>) mapGenerator.getInnerGenerator();
176    }
177
178    @Override
179    public SortedSet<K> create(Object... elements) {
180      return (SortedSet<K>) super.create(elements);
181    }
182
183    @Override
184    public K belowSamplesLesser() {
185      return delegate.belowSamplesLesser().getKey();
186    }
187
188    @Override
189    public K belowSamplesGreater() {
190      return delegate.belowSamplesGreater().getKey();
191    }
192
193    @Override
194    public K aboveSamplesLesser() {
195      return delegate.aboveSamplesLesser().getKey();
196    }
197
198    @Override
199    public K aboveSamplesGreater() {
200      return delegate.aboveSamplesGreater().getKey();
201    }
202  }
203
204  public static class MapValueCollectionGenerator<
205          K extends @Nullable Object, V extends @Nullable Object>
206      implements TestCollectionGenerator<V>, DerivedGenerator {
207    private final OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator;
208    private final SampleElements<V> samples;
209
210    public MapValueCollectionGenerator(
211        OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator) {
212      this.mapGenerator = mapGenerator;
213      SampleElements<Entry<K, V>> mapSamples = this.mapGenerator.samples();
214      this.samples =
215          new SampleElements<>(
216              mapSamples.e0().getValue(),
217              mapSamples.e1().getValue(),
218              mapSamples.e2().getValue(),
219              mapSamples.e3().getValue(),
220              mapSamples.e4().getValue());
221    }
222
223    @Override
224    public SampleElements<V> samples() {
225      return samples;
226    }
227
228    @Override
229    public Collection<V> create(Object... elements) {
230      @SuppressWarnings("unchecked")
231      V[] valuesArray = (V[]) elements;
232
233      // Start with a suitably shaped collection of entries
234      Collection<Entry<K, V>> originalEntries = mapGenerator.getSampleElements(elements.length);
235
236      // Create a copy of that, with the desired value for each value
237      Collection<Entry<K, V>> entries = new ArrayList<>(elements.length);
238      int i = 0;
239      for (Entry<K, V> entry : originalEntries) {
240        entries.add(mapEntry(entry.getKey(), valuesArray[i++]));
241      }
242
243      return mapGenerator.create(entries.toArray()).values();
244    }
245
246    @Override
247    public V[] createArray(int length) {
248      // noinspection UnnecessaryLocalVariable
249      V[] vs = ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createValueArray(length);
250      return vs;
251    }
252
253    @Override
254    public Iterable<V> order(List<V> insertionOrder) {
255      List<Entry<K, V>> orderedEntries =
256          castOrCopyToList(mapGenerator.order(castOrCopyToList(mapGenerator.getSampleElements(5))));
257      sort(
258          insertionOrder,
259          new Comparator<V>() {
260            @Override
261            public int compare(V left, V right) {
262              // The indexes are small enough for the subtraction trick to be safe.
263              return indexOfEntryWithValue(left) - indexOfEntryWithValue(right);
264            }
265
266            int indexOfEntryWithValue(V value) {
267              for (int i = 0; i < orderedEntries.size(); i++) {
268                if (equal(orderedEntries.get(i).getValue(), value)) {
269                  return i;
270                }
271              }
272              throw new IllegalArgumentException(
273                  "Map.values generator can order only sample values");
274            }
275          });
276      return insertionOrder;
277    }
278
279    @Override
280    public OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> getInnerGenerator() {
281      return mapGenerator;
282    }
283  }
284
285  // TODO(cpovirk): could something like this be used elsewhere, e.g., ReserializedListGenerator?
286  static class ForwardingTestMapGenerator<K extends @Nullable Object, V extends @Nullable Object>
287      implements TestMapGenerator<K, V> {
288    TestMapGenerator<K, V> delegate;
289
290    ForwardingTestMapGenerator(TestMapGenerator<K, V> delegate) {
291      this.delegate = delegate;
292    }
293
294    @Override
295    public Iterable<Entry<K, V>> order(List<Entry<K, V>> insertionOrder) {
296      return delegate.order(insertionOrder);
297    }
298
299    @Override
300    public K[] createKeyArray(int length) {
301      return delegate.createKeyArray(length);
302    }
303
304    @Override
305    public V[] createValueArray(int length) {
306      return delegate.createValueArray(length);
307    }
308
309    @Override
310    public SampleElements<Entry<K, V>> samples() {
311      return delegate.samples();
312    }
313
314    @Override
315    public Map<K, V> create(Object... elements) {
316      return delegate.create(elements);
317    }
318
319    @Override
320    public Entry<K, V>[] createArray(int length) {
321      return delegate.createArray(length);
322    }
323  }
324
325  /** Two bounds (from and to) define how to build a subMap. */
326  public enum Bound {
327    INCLUSIVE,
328    EXCLUSIVE,
329    NO_BOUND;
330  }
331
332  public static class SortedSetSubsetTestSetGenerator<E extends @Nullable Object>
333      implements TestSortedSetGenerator<E> {
334    final Bound to;
335    final Bound from;
336    final E firstInclusive;
337    final E lastInclusive;
338    private final Comparator<? super E> comparator;
339    private final TestSortedSetGenerator<E> delegate;
340
341    public SortedSetSubsetTestSetGenerator(
342        TestSortedSetGenerator<E> delegate, Bound to, Bound from) {
343      this.to = to;
344      this.from = from;
345      this.delegate = delegate;
346
347      SortedSet<E> emptySet = delegate.create();
348      this.comparator = emptySet.comparator();
349
350      SampleElements<E> samples = delegate.samples();
351      List<E> samplesList = new ArrayList<>(samples.asList());
352      sort(samplesList, comparator);
353      this.firstInclusive = samplesList.get(0);
354      this.lastInclusive = samplesList.get(samplesList.size() - 1);
355    }
356
357    public final TestSortedSetGenerator<E> getInnerGenerator() {
358      return delegate;
359    }
360
361    public final Bound getTo() {
362      return to;
363    }
364
365    public final Bound getFrom() {
366      return from;
367    }
368
369    @Override
370    public SampleElements<E> samples() {
371      return delegate.samples();
372    }
373
374    @Override
375    public E[] createArray(int length) {
376      return delegate.createArray(length);
377    }
378
379    @Override
380    public Iterable<E> order(List<E> insertionOrder) {
381      return delegate.order(insertionOrder);
382    }
383
384    @Override
385    public SortedSet<E> create(Object... elements) {
386      List<?> normalValues = (List<?>) asList(elements);
387      List<E> extremeValues = new ArrayList<>();
388
389      // nulls are usually out of bounds for a subset, so ban them altogether
390      for (Object o : elements) {
391        if (o == null) {
392          throw new NullPointerException();
393        }
394      }
395
396      // prepare extreme values to be filtered out of view
397      E firstExclusive = delegate.belowSamplesGreater();
398      E lastExclusive = delegate.aboveSamplesLesser();
399      if (from != Bound.NO_BOUND) {
400        extremeValues.add(delegate.belowSamplesLesser());
401        extremeValues.add(delegate.belowSamplesGreater());
402      }
403      if (to != Bound.NO_BOUND) {
404        extremeValues.add(delegate.aboveSamplesLesser());
405        extremeValues.add(delegate.aboveSamplesGreater());
406      }
407
408      // the regular values should be visible after filtering
409      List<@Nullable Object> allEntries = new ArrayList<>();
410      allEntries.addAll(extremeValues);
411      allEntries.addAll(normalValues);
412      SortedSet<E> set = delegate.create(allEntries.toArray());
413
414      return createSubSet(set, firstExclusive, lastExclusive);
415    }
416
417    /** Calls the smallest subSet overload that filters out the extreme values. */
418    SortedSet<E> createSubSet(SortedSet<E> set, E firstExclusive, E lastExclusive) {
419      if (from == Bound.NO_BOUND && to == Bound.EXCLUSIVE) {
420        return set.headSet(lastExclusive);
421      } else if (from == Bound.INCLUSIVE && to == Bound.NO_BOUND) {
422        return set.tailSet(firstInclusive);
423      } else if (from == Bound.INCLUSIVE && to == Bound.EXCLUSIVE) {
424        return set.subSet(firstInclusive, lastExclusive);
425      } else {
426        throw new IllegalArgumentException();
427      }
428    }
429
430    @Override
431    public E belowSamplesLesser() {
432      throw new UnsupportedOperationException();
433    }
434
435    @Override
436    public E belowSamplesGreater() {
437      throw new UnsupportedOperationException();
438    }
439
440    @Override
441    public E aboveSamplesLesser() {
442      throw new UnsupportedOperationException();
443    }
444
445    @Override
446    public E aboveSamplesGreater() {
447      throw new UnsupportedOperationException();
448    }
449  }
450
451  /*
452   * TODO(cpovirk): surely we can find a less ugly solution than a class that accepts 3 parameters,
453   * exposes as many getters, does work in the constructor, and has both a superclass and a subclass
454   */
455  public static class SortedMapSubmapTestMapGenerator<
456          K extends @Nullable Object, V extends @Nullable Object>
457      extends ForwardingTestMapGenerator<K, V> implements TestSortedMapGenerator<K, V> {
458    final Bound to;
459    final Bound from;
460    final K firstInclusive;
461    final K lastInclusive;
462    private final Comparator<Entry<K, V>> entryComparator;
463
464    public SortedMapSubmapTestMapGenerator(
465        TestSortedMapGenerator<K, V> delegate, Bound to, Bound from) {
466      super(delegate);
467      this.to = to;
468      this.from = from;
469
470      SortedMap<K, V> emptyMap = delegate.create();
471      this.entryComparator = entryComparator(emptyMap.comparator());
472
473      // derive values for inclusive filtering from the input samples
474      SampleElements<Entry<K, V>> samples = delegate.samples();
475      List<Entry<K, V>> samplesList =
476          asList(samples.e0(), samples.e1(), samples.e2(), samples.e3(), samples.e4());
477      sort(samplesList, entryComparator);
478      this.firstInclusive = samplesList.get(0).getKey();
479      this.lastInclusive = samplesList.get(samplesList.size() - 1).getKey();
480    }
481
482    @Override
483    public SortedMap<K, V> create(Object... entries) {
484      List<Entry<K, V>> extremeValues = new ArrayList<>();
485
486      // prepare extreme values to be filtered out of view
487      K firstExclusive = getInnerGenerator().belowSamplesGreater().getKey();
488      K lastExclusive = getInnerGenerator().aboveSamplesLesser().getKey();
489      if (from != Bound.NO_BOUND) {
490        extremeValues.add(getInnerGenerator().belowSamplesLesser());
491        extremeValues.add(getInnerGenerator().belowSamplesGreater());
492      }
493      if (to != Bound.NO_BOUND) {
494        extremeValues.add(getInnerGenerator().aboveSamplesLesser());
495        extremeValues.add(getInnerGenerator().aboveSamplesGreater());
496      }
497
498      // the regular values should be visible after filtering
499      List<Entry<?, ?>> allEntries = new ArrayList<>();
500      allEntries.addAll(extremeValues);
501      for (Object entry : entries) {
502        allEntries.add((Entry<?, ?>) entry);
503      }
504      SortedMap<K, V> map = (SortedMap<K, V>) delegate.create(allEntries.toArray());
505
506      return createSubMap(map, firstExclusive, lastExclusive);
507    }
508
509    /**
510     * Calls the smallest subMap overload that filters out the extreme values. This method is
511     * overridden in NavigableMapTestSuiteBuilder.
512     */
513    SortedMap<K, V> createSubMap(SortedMap<K, V> map, K firstExclusive, K lastExclusive) {
514      if (from == Bound.NO_BOUND && to == Bound.EXCLUSIVE) {
515        return map.headMap(lastExclusive);
516      } else if (from == Bound.INCLUSIVE && to == Bound.NO_BOUND) {
517        return map.tailMap(firstInclusive);
518      } else if (from == Bound.INCLUSIVE && to == Bound.EXCLUSIVE) {
519        return map.subMap(firstInclusive, lastExclusive);
520      } else {
521        throw new IllegalArgumentException();
522      }
523    }
524
525    public final Bound getTo() {
526      return to;
527    }
528
529    public final Bound getFrom() {
530      return from;
531    }
532
533    public final TestSortedMapGenerator<K, V> getInnerGenerator() {
534      return (TestSortedMapGenerator<K, V>) delegate;
535    }
536
537    @Override
538    public Entry<K, V> belowSamplesLesser() {
539      // should never reach here!
540      throw new UnsupportedOperationException();
541    }
542
543    @Override
544    public Entry<K, V> belowSamplesGreater() {
545      // should never reach here!
546      throw new UnsupportedOperationException();
547    }
548
549    @Override
550    public Entry<K, V> aboveSamplesLesser() {
551      // should never reach here!
552      throw new UnsupportedOperationException();
553    }
554
555    @Override
556    public Entry<K, V> aboveSamplesGreater() {
557      // should never reach here!
558      throw new UnsupportedOperationException();
559    }
560  }
561
562  private DerivedCollectionGenerators() {}
563}