/*
* 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 com.google.common.collect.testing.DerivedCollectionGenerators.SortedMapSubmapTestMapGenerator;
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;
/**
* 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);
}
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.
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;
}
public OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>> getInnerGenerator() {
return mapGenerator;
}
}
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;
}
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;
}
/*
* 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> {
final Bound to;
final Bound from;
final K firstInclusive;
final K lastInclusive;
private final Comparator<Entry<K, V>> entryComparator;
public SortedMapSubmapTestMapGenerator(TestMapGenerator<K, V> delegate, Bound to, Bound from) {
super(delegate);
this.to = to;
this.from = from;
SortedMap<K, V> emptyMap = (SortedMap<K, V>) 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 Map<K, V> create(Object... entries) {
@SuppressWarnings("unchecked") // we dangerously assume K and V are both strings
List<Entry<K, V>> extremeValues = (List) getExtremeValues();
@SuppressWarnings("unchecked") // map generators must past entry objects
List<Entry<K, V>> normalValues = (List) Arrays.asList(entries);
// prepare extreme values to be filtered out of view
Collections.sort(extremeValues, entryComparator);
K firstExclusive = extremeValues.get(1).getKey();
K lastExclusive = extremeValues.get(2).getKey();
if (from == Bound.NO_BOUND) {
extremeValues.remove(0);
extremeValues.remove(0);
}
if (to == Bound.NO_BOUND) {
extremeValues.remove(extremeValues.size() - 1);
extremeValues.remove(extremeValues.size() - 1);
}
// 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.
*/
Map<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 TestMapGenerator<K, V> getInnerGenerator() {
return delegate;
}
}
/**
* Returns an array of four bogus elements that will always be too high or
* too low for the display. This includes two values for each extreme.
*
* <p>This method (dangerously) assume that the strings {@code "!! a"} and
* {@code "~~ z"} will work for this purpose, which may cause problems for
* navigable maps with non-string or unicode generators.
*/
private static List<Entry<String, String>> getExtremeValues() {
List<Entry<String, String>> result = new ArrayList<Entry<String, String>>();
result.add(Helpers.mapEntry("!! a", "below view"));
result.add(Helpers.mapEntry("!! b", "below view"));
result.add(Helpers.mapEntry("~~ y", "above view"));
result.add(Helpers.mapEntry("~~ z", "above view"));
return result;
}
private DerivedCollectionGenerators() {}
}