/* * Copyright (C) 2008 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.collect.testing; import static com.google.common.collect.testing.Helpers.castOrCopyToList; import static com.google.common.collect.testing.Helpers.equal; import static com.google.common.collect.testing.Helpers.mapEntry; import static java.util.Collections.sort; import com.google.common.annotations.GwtCompatible; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; /** * Derived suite generators, split out of the suite builders so that they are available to GWT. * * @author George van den Driessche */ @GwtCompatible public final class DerivedCollectionGenerators { public static class MapEntrySetGenerator<K, V> implements TestSetGenerator<Map.Entry<K, V>>, DerivedGenerator { private final OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> mapGenerator; public MapEntrySetGenerator( OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> mapGenerator) { this.mapGenerator = mapGenerator; } @Override public SampleElements<Map.Entry<K, V>> samples() { return mapGenerator.samples(); } @Override public Set<Map.Entry<K, V>> create(Object... elements) { return mapGenerator.create(elements).entrySet(); } @Override public Map.Entry<K, V>[] createArray(int length) { return mapGenerator.createArray(length); } @Override public Iterable<Map.Entry<K, V>> order(List<Map.Entry<K, V>> insertionOrder) { return mapGenerator.order(insertionOrder); } @Override public OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> getInnerGenerator() { return mapGenerator; } } // TODO: investigate some API changes to SampleElements that would tidy up // parts of the following classes. static <K, V> TestSetGenerator<K> keySetGenerator( OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> mapGenerator) { TestContainerGenerator<Map<K, V>, Entry<K, V>> generator = mapGenerator.getInnerGenerator(); if (generator instanceof TestSortedMapGenerator && ((TestSortedMapGenerator<K, V>) generator).create().keySet() instanceof SortedSet) { return new MapSortedKeySetGenerator<K, V>(mapGenerator); } else { return new MapKeySetGenerator<K, V>(mapGenerator); } } public static class MapKeySetGenerator<K, V> implements TestSetGenerator<K>, DerivedGenerator { private final OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> mapGenerator; private final SampleElements<K> samples; public MapKeySetGenerator( OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> mapGenerator) { this.mapGenerator = mapGenerator; final SampleElements<Map.Entry<K, V>> mapSamples = this.mapGenerator.samples(); this.samples = new SampleElements<K>( mapSamples.e0().getKey(), mapSamples.e1().getKey(), mapSamples.e2().getKey(), mapSamples.e3().getKey(), mapSamples.e4().getKey()); } @Override public SampleElements<K> samples() { return samples; } @Override public Set<K> create(Object... elements) { @SuppressWarnings("unchecked") K[] keysArray = (K[]) elements; // Start with a suitably shaped collection of entries Collection<Map.Entry<K, V>> originalEntries = mapGenerator.getSampleElements(elements.length); // Create a copy of that, with the desired value for each key Collection<Map.Entry<K, V>> entries = new ArrayList<Entry<K, V>>(elements.length); int i = 0; for (Map.Entry<K, V> entry : originalEntries) { entries.add(Helpers.mapEntry(keysArray[i++], entry.getValue())); } return mapGenerator.create(entries.toArray()).keySet(); } @Override public K[] createArray(int length) { // TODO: with appropriate refactoring of OneSizeGenerator, we can perhaps // tidy this up and get rid of the casts here and in // MapValueCollectionGenerator. return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createKeyArray(length); } @Override public Iterable<K> order(List<K> insertionOrder) { V v = ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).samples().e0().getValue(); List<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(); for (K element : insertionOrder) { entries.add(mapEntry(element, v)); } List<K> keys = new ArrayList<K>(); for (Entry<K, V> entry : mapGenerator.order(entries)) { keys.add(entry.getKey()); } return keys; } @Override public OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> getInnerGenerator() { return mapGenerator; } } public static class MapSortedKeySetGenerator<K, V> extends MapKeySetGenerator<K, V> implements TestSortedSetGenerator<K>, DerivedGenerator { private final TestSortedMapGenerator<K, V> delegate; public MapSortedKeySetGenerator( OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator) { super(mapGenerator); this.delegate = (TestSortedMapGenerator<K, V>) mapGenerator.getInnerGenerator(); } @Override public SortedSet<K> create(Object... elements) { return (SortedSet<K>) super.create(elements); } @Override public K belowSamplesLesser() { return delegate.belowSamplesLesser().getKey(); } @Override public K belowSamplesGreater() { return delegate.belowSamplesGreater().getKey(); } @Override public K aboveSamplesLesser() { return delegate.aboveSamplesLesser().getKey(); } @Override public K aboveSamplesGreater() { return delegate.aboveSamplesGreater().getKey(); } } public static class MapValueCollectionGenerator<K, V> implements TestCollectionGenerator<V>, DerivedGenerator { private final OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> mapGenerator; private final SampleElements<V> samples; public MapValueCollectionGenerator( OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> mapGenerator) { this.mapGenerator = mapGenerator; final SampleElements<Map.Entry<K, V>> mapSamples = this.mapGenerator.samples(); this.samples = new SampleElements<V>( mapSamples.e0().getValue(), mapSamples.e1().getValue(), mapSamples.e2().getValue(), mapSamples.e3().getValue(), mapSamples.e4().getValue()); } @Override public SampleElements<V> samples() { return samples; } @Override public Collection<V> create(Object... elements) { @SuppressWarnings("unchecked") V[] valuesArray = (V[]) elements; // Start with a suitably shaped collection of entries Collection<Map.Entry<K, V>> originalEntries = mapGenerator.getSampleElements(elements.length); // Create a copy of that, with the desired value for each value Collection<Map.Entry<K, V>> entries = new ArrayList<Entry<K, V>>(elements.length); int i = 0; for (Map.Entry<K, V> entry : originalEntries) { entries.add(Helpers.mapEntry(entry.getKey(), valuesArray[i++])); } return mapGenerator.create(entries.toArray()).values(); } @Override public V[] createArray(int length) { //noinspection UnnecessaryLocalVariable final V[] vs = ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createValueArray(length); return vs; } @Override public Iterable<V> order(List<V> insertionOrder) { final List<Entry<K, V>> orderedEntries = castOrCopyToList(mapGenerator.order(castOrCopyToList(mapGenerator.getSampleElements(5)))); sort( insertionOrder, new Comparator<V>() { @Override public int compare(V left, V right) { // The indexes are small enough for the subtraction trick to be safe. return indexOfEntryWithValue(left) - indexOfEntryWithValue(right); } int indexOfEntryWithValue(V value) { for (int i = 0; i < orderedEntries.size(); i++) { if (equal(orderedEntries.get(i).getValue(), value)) { return i; } } throw new IllegalArgumentException( "Map.values generator can order only sample values"); } }); return insertionOrder; } @Override public OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> getInnerGenerator() { return mapGenerator; } } // TODO(cpovirk): could something like this be used elsewhere, e.g., ReserializedListGenerator? static class ForwardingTestMapGenerator<K, V> implements TestMapGenerator<K, V> { TestMapGenerator<K, V> delegate; ForwardingTestMapGenerator(TestMapGenerator<K, V> delegate) { this.delegate = delegate; } @Override public Iterable<Entry<K, V>> order(List<Entry<K, V>> insertionOrder) { return delegate.order(insertionOrder); } @Override public K[] createKeyArray(int length) { return delegate.createKeyArray(length); } @Override public V[] createValueArray(int length) { return delegate.createValueArray(length); } @Override public SampleElements<Entry<K, V>> samples() { return delegate.samples(); } @Override public Map<K, V> create(Object... elements) { return delegate.create(elements); } @Override public Entry<K, V>[] createArray(int length) { return delegate.createArray(length); } } /** * Two bounds (from and to) define how to build a subMap. */ public enum Bound { INCLUSIVE, EXCLUSIVE, NO_BOUND; } public static class SortedSetSubsetTestSetGenerator<E> implements TestSortedSetGenerator<E> { final Bound to; final Bound from; final E firstInclusive; final E lastInclusive; private final Comparator<? super E> comparator; private final TestSortedSetGenerator<E> delegate; public SortedSetSubsetTestSetGenerator( TestSortedSetGenerator<E> delegate, Bound to, Bound from) { this.to = to; this.from = from; this.delegate = delegate; SortedSet<E> emptySet = delegate.create(); this.comparator = emptySet.comparator(); SampleElements<E> samples = delegate.samples(); List<E> samplesList = new ArrayList<E>(samples.asList()); Collections.sort(samplesList, comparator); this.firstInclusive = samplesList.get(0); this.lastInclusive = samplesList.get(samplesList.size() - 1); } public final TestSortedSetGenerator<E> getInnerGenerator() { return delegate; } public final Bound getTo() { return to; } public final Bound getFrom() { return from; } @Override public SampleElements<E> samples() { return delegate.samples(); } @Override public E[] createArray(int length) { return delegate.createArray(length); } @Override public Iterable<E> order(List<E> insertionOrder) { return delegate.order(insertionOrder); } @Override public SortedSet<E> create(Object... elements) { @SuppressWarnings("unchecked") // set generators must pass SampleElements values List<E> normalValues = (List) Arrays.asList(elements); List<E> extremeValues = new ArrayList<E>(); // nulls are usually out of bounds for a subset, so ban them altogether for (Object o : elements) { if (o == null) { throw new NullPointerException(); } } // prepare extreme values to be filtered out of view E firstExclusive = delegate.belowSamplesGreater(); E lastExclusive = delegate.aboveSamplesLesser(); if (from != Bound.NO_BOUND) { extremeValues.add(delegate.belowSamplesLesser()); extremeValues.add(delegate.belowSamplesGreater()); } if (to != Bound.NO_BOUND) { extremeValues.add(delegate.aboveSamplesLesser()); extremeValues.add(delegate.aboveSamplesGreater()); } // the regular values should be visible after filtering List<E> allEntries = new ArrayList<E>(); allEntries.addAll(extremeValues); allEntries.addAll(normalValues); SortedSet<E> map = delegate.create(allEntries.toArray()); return createSubSet(map, firstExclusive, lastExclusive); } /** * Calls the smallest subSet overload that filters out the extreme values. */ SortedSet<E> createSubSet(SortedSet<E> set, E firstExclusive, E lastExclusive) { if (from == Bound.NO_BOUND && to == Bound.EXCLUSIVE) { return set.headSet(lastExclusive); } else if (from == Bound.INCLUSIVE && to == Bound.NO_BOUND) { return set.tailSet(firstInclusive); } else if (from == Bound.INCLUSIVE && to == Bound.EXCLUSIVE) { return set.subSet(firstInclusive, lastExclusive); } else { throw new IllegalArgumentException(); } } @Override public E belowSamplesLesser() { throw new UnsupportedOperationException(); } @Override public E belowSamplesGreater() { throw new UnsupportedOperationException(); } @Override public E aboveSamplesLesser() { throw new UnsupportedOperationException(); } @Override public E aboveSamplesGreater() { throw new UnsupportedOperationException(); } } /* * TODO(cpovirk): surely we can find a less ugly solution than a class that accepts 3 parameters, * exposes as many getters, does work in the constructor, and has both a superclass and a subclass */ public static class SortedMapSubmapTestMapGenerator<K, V> extends ForwardingTestMapGenerator<K, V> implements TestSortedMapGenerator<K, V> { final Bound to; final Bound from; final K firstInclusive; final K lastInclusive; private final Comparator<Entry<K, V>> entryComparator; public SortedMapSubmapTestMapGenerator( TestSortedMapGenerator<K, V> delegate, Bound to, Bound from) { super(delegate); this.to = to; this.from = from; SortedMap<K, V> emptyMap = delegate.create(); this.entryComparator = Helpers.entryComparator(emptyMap.comparator()); // derive values for inclusive filtering from the input samples SampleElements<Entry<K, V>> samples = delegate.samples(); @SuppressWarnings("unchecked") // no elements are inserted into the array List<Entry<K, V>> samplesList = Arrays.asList(samples.e0(), samples.e1(), samples.e2(), samples.e3(), samples.e4()); Collections.sort(samplesList, entryComparator); this.firstInclusive = samplesList.get(0).getKey(); this.lastInclusive = samplesList.get(samplesList.size() - 1).getKey(); } @Override public SortedMap<K, V> create(Object... entries) { @SuppressWarnings("unchecked") // map generators must past entry objects List<Entry<K, V>> normalValues = (List) Arrays.asList(entries); List<Entry<K, V>> extremeValues = new ArrayList<Entry<K, V>>(); // prepare extreme values to be filtered out of view K firstExclusive = getInnerGenerator().belowSamplesGreater().getKey(); K lastExclusive = getInnerGenerator().aboveSamplesLesser().getKey(); if (from != Bound.NO_BOUND) { extremeValues.add(getInnerGenerator().belowSamplesLesser()); extremeValues.add(getInnerGenerator().belowSamplesGreater()); } if (to != Bound.NO_BOUND) { extremeValues.add(getInnerGenerator().aboveSamplesLesser()); extremeValues.add(getInnerGenerator().aboveSamplesGreater()); } // the regular values should be visible after filtering List<Entry<K, V>> allEntries = new ArrayList<Entry<K, V>>(); allEntries.addAll(extremeValues); allEntries.addAll(normalValues); SortedMap<K, V> map = (SortedMap<K, V>) delegate.create((Object[]) allEntries.toArray(new Entry<?, ?>[allEntries.size()])); return createSubMap(map, firstExclusive, lastExclusive); } /** * Calls the smallest subMap overload that filters out the extreme values. This method is * overridden in NavigableMapTestSuiteBuilder. */ SortedMap<K, V> createSubMap(SortedMap<K, V> map, K firstExclusive, K lastExclusive) { if (from == Bound.NO_BOUND && to == Bound.EXCLUSIVE) { return map.headMap(lastExclusive); } else if (from == Bound.INCLUSIVE && to == Bound.NO_BOUND) { return map.tailMap(firstInclusive); } else if (from == Bound.INCLUSIVE && to == Bound.EXCLUSIVE) { return map.subMap(firstInclusive, lastExclusive); } else { throw new IllegalArgumentException(); } } public final Bound getTo() { return to; } public final Bound getFrom() { return from; } public final TestSortedMapGenerator<K, V> getInnerGenerator() { return (TestSortedMapGenerator<K, V>) delegate; } @Override public Entry<K, V> belowSamplesLesser() { // should never reach here! throw new UnsupportedOperationException(); } @Override public Entry<K, V> belowSamplesGreater() { // should never reach here! throw new UnsupportedOperationException(); } @Override public Entry<K, V> aboveSamplesLesser() { // should never reach here! throw new UnsupportedOperationException(); } @Override public Entry<K, V> aboveSamplesGreater() { // should never reach here! throw new UnsupportedOperationException(); } } private DerivedCollectionGenerators() {} }