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