/* * 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 implements TestSetGenerator>, DerivedGenerator { private final OneSizeTestContainerGenerator, Map.Entry> mapGenerator; public MapEntrySetGenerator( OneSizeTestContainerGenerator< Map, Map.Entry> mapGenerator) { this.mapGenerator = mapGenerator; } @Override public SampleElements> samples() { return mapGenerator.samples(); } @Override public Set> create(Object... elements) { return mapGenerator.create(elements).entrySet(); } @Override public Map.Entry[] createArray(int length) { return mapGenerator.createArray(length); } @Override public Iterable> order( List> insertionOrder) { return mapGenerator.order(insertionOrder); } @Override public OneSizeTestContainerGenerator, Map.Entry> getInnerGenerator() { return mapGenerator; } } // TODO: investigate some API changes to SampleElements that would tidy up // parts of the following classes. static TestSetGenerator keySetGenerator( OneSizeTestContainerGenerator, Map.Entry> mapGenerator) { TestContainerGenerator, Entry> generator = mapGenerator.getInnerGenerator(); if (generator instanceof TestSortedMapGenerator && ((TestSortedMapGenerator) generator).create().keySet() instanceof SortedSet) { return new MapSortedKeySetGenerator(mapGenerator); } else { return new MapKeySetGenerator(mapGenerator); } } public static class MapKeySetGenerator implements TestSetGenerator, DerivedGenerator { private final OneSizeTestContainerGenerator, Map.Entry> mapGenerator; private final SampleElements samples; public MapKeySetGenerator( OneSizeTestContainerGenerator, Map.Entry> mapGenerator) { this.mapGenerator = mapGenerator; final SampleElements> mapSamples = this.mapGenerator.samples(); this.samples = new SampleElements( mapSamples.e0.getKey(), mapSamples.e1.getKey(), mapSamples.e2.getKey(), mapSamples.e3.getKey(), mapSamples.e4.getKey()); } @Override public SampleElements samples() { return samples; } @Override public Set create(Object... elements) { @SuppressWarnings("unchecked") K[] keysArray = (K[]) elements; // Start with a suitably shaped collection of entries Collection> originalEntries = mapGenerator.getSampleElements(elements.length); // Create a copy of that, with the desired value for each key Collection> entries = new ArrayList>(elements.length); int i = 0; for (Map.Entry 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) mapGenerator.getInnerGenerator()) .createKeyArray(length); } @Override public Iterable order(List insertionOrder) { V v = ((TestMapGenerator) mapGenerator.getInnerGenerator()).samples().e0.getValue(); List> entries = new ArrayList>(); for (K element : insertionOrder) { entries.add(mapEntry(element, v)); } List keys = new ArrayList(); for (Entry entry : mapGenerator.order(entries)) { keys.add(entry.getKey()); } return keys; } @Override public OneSizeTestContainerGenerator, Map.Entry> getInnerGenerator() { return mapGenerator; } } public static class MapSortedKeySetGenerator extends MapKeySetGenerator implements TestSortedSetGenerator, DerivedGenerator { private final TestSortedMapGenerator delegate; public MapSortedKeySetGenerator( OneSizeTestContainerGenerator, Entry> mapGenerator) { super(mapGenerator); this.delegate = (TestSortedMapGenerator) mapGenerator.getInnerGenerator(); } @Override public SortedSet create(Object... elements) { return (SortedSet) 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 implements TestCollectionGenerator, DerivedGenerator { private final OneSizeTestContainerGenerator, Map.Entry> mapGenerator; private final SampleElements samples; public MapValueCollectionGenerator( OneSizeTestContainerGenerator< Map, Map.Entry> mapGenerator) { this.mapGenerator = mapGenerator; final SampleElements> mapSamples = this.mapGenerator.samples(); this.samples = new SampleElements( mapSamples.e0.getValue(), mapSamples.e1.getValue(), mapSamples.e2.getValue(), mapSamples.e3.getValue(), mapSamples.e4.getValue()); } @Override public SampleElements samples() { return samples; } @Override public Collection create(Object... elements) { @SuppressWarnings("unchecked") V[] valuesArray = (V[]) elements; // Start with a suitably shaped collection of entries Collection> originalEntries = mapGenerator.getSampleElements(elements.length); // Create a copy of that, with the desired value for each value Collection> entries = new ArrayList>(elements.length); int i = 0; for (Map.Entry 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) mapGenerator.getInnerGenerator()) .createValueArray(length); return vs; } @Override public Iterable order(List insertionOrder) { final List> orderedEntries = castOrCopyToList(mapGenerator.order(castOrCopyToList( mapGenerator.getSampleElements(5)))); sort(insertionOrder, new Comparator() { @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.Entry> getInnerGenerator() { return mapGenerator; } } // TODO(cpovirk): could something like this be used elsewhere, e.g., ReserializedListGenerator? static class ForwardingTestMapGenerator implements TestMapGenerator { TestMapGenerator delegate; ForwardingTestMapGenerator(TestMapGenerator delegate) { this.delegate = delegate; } @Override public Iterable> order(List> 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> samples() { return delegate.samples(); } @Override public Map create(Object... elements) { return delegate.create(elements); } @Override public Entry[] 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 implements TestSortedSetGenerator { final Bound to; final Bound from; final E firstInclusive; final E lastInclusive; private final Comparator comparator; private final TestSortedSetGenerator delegate; public SortedSetSubsetTestSetGenerator( TestSortedSetGenerator delegate, Bound to, Bound from) { this.to = to; this.from = from; this.delegate = delegate; SortedSet emptySet = delegate.create(); this.comparator = emptySet.comparator(); SampleElements samples = delegate.samples(); List samplesList = new ArrayList(samples.asList()); Collections.sort(samplesList, comparator); this.firstInclusive = samplesList.get(0); this.lastInclusive = samplesList.get(samplesList.size() - 1); } public final TestSortedSetGenerator getInnerGenerator() { return delegate; } public final Bound getTo() { return to; } public final Bound getFrom() { return from; } @Override public SampleElements samples() { return delegate.samples(); } @Override public E[] createArray(int length) { return delegate.createArray(length); } @Override public Iterable order(List insertionOrder) { return delegate.order(insertionOrder); } @Override public SortedSet create(Object... elements) { @SuppressWarnings("unchecked") // set generators must pass SampleElements values List normalValues = (List) Arrays.asList(elements); List extremeValues = new ArrayList(); // 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 allEntries = new ArrayList(); allEntries.addAll(extremeValues); allEntries.addAll(normalValues); SortedSet map = delegate.create(allEntries.toArray()); return createSubSet(map, firstExclusive, lastExclusive); } /** * Calls the smallest subSet overload that filters out the extreme values. */ SortedSet createSubSet(SortedSet 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 extends ForwardingTestMapGenerator implements TestSortedMapGenerator { final Bound to; final Bound from; final K firstInclusive; final K lastInclusive; private final Comparator> entryComparator; public SortedMapSubmapTestMapGenerator( TestSortedMapGenerator delegate, Bound to, Bound from) { super(delegate); this.to = to; this.from = from; SortedMap emptyMap = delegate.create(); this.entryComparator = Helpers.entryComparator(emptyMap.comparator()); // derive values for inclusive filtering from the input samples SampleElements> samples = delegate.samples(); @SuppressWarnings("unchecked") // no elements are inserted into the array List> 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 create(Object... entries) { @SuppressWarnings("unchecked") // map generators must past entry objects List> normalValues = (List) Arrays.asList(entries); List> extremeValues = new ArrayList>(); // 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> allEntries = new ArrayList>(); allEntries.addAll(extremeValues); allEntries.addAll(normalValues); SortedMap map = (SortedMap) 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 createSubMap(SortedMap 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 getInnerGenerator() { return (TestSortedMapGenerator) delegate; } @Override public Entry belowSamplesLesser() { // should never reach here! throw new UnsupportedOperationException(); } @Override public Entry belowSamplesGreater() { // should never reach here! throw new UnsupportedOperationException(); } @Override public Entry aboveSamplesLesser() { // should never reach here! throw new UnsupportedOperationException(); } @Override public Entry aboveSamplesGreater() { // should never reach here! throw new UnsupportedOperationException(); } } private DerivedCollectionGenerators() {} }