All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.testing.FreshValueGenerator Maven / Gradle / Ivy

Go to download

Guava testlib is a set of java classes used for more convenient unit testing - particularly to assist the tests for Guava itself.

There is a newer version: 33.3.1-jre
Show newest version
/*
 * Copyright (C) 2012 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.testing;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.throwIfUnchecked;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.J2ktIncompatible;
import com.google.common.base.CharMatcher;
import com.google.common.base.Equivalence;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Ticker;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.LinkedHashMultiset;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.collect.Ordering;
import com.google.common.collect.Range;
import com.google.common.collect.RowSortedTable;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.collect.SortedMultiset;
import com.google.common.collect.Table;
import com.google.common.collect.TreeBasedTable;
import com.google.common.collect.TreeMultiset;
import com.google.common.primitives.Primitives;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.primitives.UnsignedLong;
import com.google.common.reflect.AbstractInvocationHandler;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.Reflection;
import com.google.common.reflect.TypeToken;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Currency;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Generates fresh instances of types that are different from each other (if possible).
 *
 * @author Ben Yu
 */
@GwtIncompatible
@J2ktIncompatible
class FreshValueGenerator {

  private static final ImmutableMap, Method> GENERATORS;

  static {
    ImmutableMap.Builder, Method> builder = ImmutableMap.builder();
    for (Method method : FreshValueGenerator.class.getDeclaredMethods()) {
      if (method.isAnnotationPresent(Generates.class)) {
        builder.put(method.getReturnType(), method);
      }
    }
    GENERATORS = builder.buildOrThrow();
  }

  private static final ImmutableMap, Method> EMPTY_GENERATORS;

  static {
    ImmutableMap.Builder, Method> builder = ImmutableMap.builder();
    for (Method method : FreshValueGenerator.class.getDeclaredMethods()) {
      if (method.isAnnotationPresent(Empty.class)) {
        builder.put(method.getReturnType(), method);
      }
    }
    EMPTY_GENERATORS = builder.buildOrThrow();
  }

  private final AtomicInteger freshness = new AtomicInteger(1);
  private final ListMultimap, Object> sampleInstances = ArrayListMultimap.create();

  /**
   * The freshness level at which the {@link Empty @Empty} annotated method was invoked to generate
   * instance.
   */
  private final Map emptyInstanceGenerated = new HashMap<>();

  final  void addSampleInstances(Class type, Iterable instances) {
    sampleInstances.putAll(checkNotNull(type), checkNotNull(instances));
  }

  /**
   * Returns a fresh instance for {@code type} if possible. The returned instance could be:
   *
   * 
    *
  • exactly of the given type, including generic type parameters, such as {@code * ImmutableList}; *
  • of the raw type; *
  • null if no value can be generated. *
*/ final @Nullable Object generateFresh(TypeToken type) { Object generated = generate(type); if (generated != null) { freshness.incrementAndGet(); } return generated; } final @Nullable T generateFresh(Class type) { return Primitives.wrap(type).cast(generateFresh(TypeToken.of(type))); } final T newFreshProxy(final Class interfaceType) { T proxy = newProxy(interfaceType); freshness.incrementAndGet(); return proxy; } /** * Generates an instance for {@code type} using the current {@link #freshness}. The generated * instance may or may not be unique across different calls. */ private @Nullable Object generate(TypeToken type) { Class rawType = type.getRawType(); List samples = sampleInstances.get(rawType); Object sample = pickInstance(samples, null); if (sample != null) { return sample; } if (rawType.isEnum()) { return pickInstance(rawType.getEnumConstants(), null); } if (type.isArray()) { TypeToken componentType = requireNonNull(type.getComponentType()); Object array = Array.newInstance(componentType.getRawType(), 1); Array.set(array, 0, generate(componentType)); return array; } Method emptyGenerate = EMPTY_GENERATORS.get(rawType); if (emptyGenerate != null) { if (emptyInstanceGenerated.containsKey(type.getType())) { // empty instance already generated if (emptyInstanceGenerated.get(type.getType()).intValue() == freshness.get()) { // same freshness, generate again. return invokeGeneratorMethod(emptyGenerate); } else { // Cannot use empty generator. Proceed with other generators. } } else { // never generated empty instance for this type before. Object emptyInstance = invokeGeneratorMethod(emptyGenerate); emptyInstanceGenerated.put(type.getType(), freshness.get()); return emptyInstance; } } Method generate = GENERATORS.get(rawType); if (generate != null) { ImmutableList params = Invokable.from(generate).getParameters(); List args = Lists.newArrayListWithCapacity(params.size()); TypeVariable[] typeVars = rawType.getTypeParameters(); for (int i = 0; i < params.size(); i++) { TypeToken paramType = type.resolveType(typeVars[i]); // We require all @Generates methods to either be parameter-less or accept non-null // values for their generic parameter types. Object argValue = generate(paramType); if (argValue == null) { // When a parameter of a @Generates method cannot be created, // The type most likely is a collection. // Our distinct proxy doesn't work for collections. // So just refuse to generate. return null; } args.add(argValue); } return invokeGeneratorMethod(generate, args.toArray()); } return defaultGenerate(rawType); } private @Nullable T defaultGenerate(Class rawType) { if (rawType.isInterface()) { // always create a new proxy return newProxy(rawType); } return ArbitraryInstances.get(rawType); } private T newProxy(final Class interfaceType) { return Reflection.newProxy(interfaceType, new FreshInvocationHandler(interfaceType)); } private Object invokeGeneratorMethod(Method generator, Object... args) { try { return generator.invoke(this, args); } catch (InvocationTargetException e) { throwIfUnchecked(e.getCause()); throw new RuntimeException(e.getCause()); } catch (Exception e) { throwIfUnchecked(e); throw new RuntimeException(e); } } private final class FreshInvocationHandler extends AbstractInvocationHandler { private final int identity = generateInt(); private final Class interfaceType; FreshInvocationHandler(Class interfaceType) { this.interfaceType = interfaceType; } @Override @CheckForNull protected Object handleInvocation(Object proxy, Method method, @Nullable Object[] args) { return interfaceMethodCalled(interfaceType, method); } @Override public int hashCode() { return identity; } @Override public boolean equals(@Nullable Object obj) { if (obj instanceof FreshInvocationHandler) { FreshInvocationHandler that = (FreshInvocationHandler) obj; return identity == that.identity; } return false; } @Override public String toString() { return paramString(interfaceType, identity); } } /** Subclasses can override to provide different return value for proxied interface methods. */ @CheckForNull Object interfaceMethodCalled(Class interfaceType, Method method) { throw new UnsupportedOperationException(); } private T pickInstance(T[] instances, T defaultValue) { return pickInstance(Arrays.asList(instances), defaultValue); } private T pickInstance(Collection instances, T defaultValue) { if (instances.isEmpty()) { return defaultValue; } // generateInt() is 1-based. return Iterables.get(instances, (generateInt() - 1) % instances.size()); } private static String paramString(Class type, int i) { return type.getSimpleName() + '@' + i; } /** * Annotates a method to be the instance generator of a certain type. The return type is the * generated type. The method parameters correspond to the generated type's type parameters. For * example, if the annotated method returns {@code Map}, the method signature should be: * {@code Map generateMap(K key, V value)}. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) private @interface Generates {} /** * Annotates a method to generate the "empty" instance of a collection. This method should accept * no parameter. The value it generates should be unequal to the values generated by methods * annotated with {@link Generates}. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) private @interface Empty {} @Generates Class generateClass() { return pickInstance( ImmutableList.of( int.class, long.class, void.class, Object.class, Object[].class, Iterable.class), Object.class); } @Generates Object generateObject() { return generateString(); } @Generates Number generateNumber() { return generateInt(); } @Generates int generateInt() { return freshness.get(); } @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates Integer generateInteger() { return new Integer(generateInt()); } @Generates long generateLong() { return generateInt(); } @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates Long generateLongObject() { return new Long(generateLong()); } @Generates float generateFloat() { return generateInt(); } @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates Float generateFloatObject() { return new Float(generateFloat()); } @Generates double generateDouble() { return generateInt(); } @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates Double generateDoubleObject() { return new Double(generateDouble()); } @Generates short generateShort() { return (short) generateInt(); } @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates Short generateShortObject() { return new Short(generateShort()); } @Generates byte generateByte() { return (byte) generateInt(); } @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates Byte generateByteObject() { return new Byte(generateByte()); } @Generates char generateChar() { return generateString().charAt(0); } @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates Character generateCharacter() { return new Character(generateChar()); } @Generates boolean generateBoolean() { return generateInt() % 2 == 0; } @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates Boolean generateBooleanObject() { return new Boolean(generateBoolean()); } @Generates UnsignedInteger generateUnsignedInteger() { return UnsignedInteger.fromIntBits(generateInt()); } @Generates UnsignedLong generateUnsignedLong() { return UnsignedLong.fromLongBits(generateLong()); } @Generates BigInteger generateBigInteger() { return BigInteger.valueOf(generateInt()); } @Generates BigDecimal generateBigDecimal() { return BigDecimal.valueOf(generateInt()); } @Generates CharSequence generateCharSequence() { return generateString(); } @Generates String generateString() { return Integer.toString(generateInt()); } @Generates Comparable generateComparable() { return generateString(); } @Generates Pattern generatePattern() { return Pattern.compile(generateString()); } @Generates Charset generateCharset() { return pickInstance(Charset.availableCharsets().values(), UTF_8); } @Generates Locale generateLocale() { return pickInstance(Locale.getAvailableLocales(), Locale.US); } @Generates Currency generateCurrency() { return pickInstance(Currency.getAvailableCurrencies(), Currency.getInstance(Locale.US)); } @Empty Optional generateJavaOptional() { return Optional.empty(); } @Generates Optional generateJavaOptional(T value) { return Optional.of(value); } @Generates OptionalInt generateOptionalInt() { return OptionalInt.of(generateInt()); } @Generates OptionalLong generateOptionalLong() { return OptionalLong.of(generateLong()); } @Generates OptionalDouble generateOptionalDouble() { return OptionalDouble.of(generateDouble()); } // common.base @Empty com.google.common.base.Optional generateGoogleOptional() { return com.google.common.base.Optional.absent(); } @Generates com.google.common.base.Optional generateGoogleOptional(T value) { return com.google.common.base.Optional.of(value); } @Generates Joiner generateJoiner() { return Joiner.on(generateString()); } @Generates Splitter generateSplitter() { return Splitter.on(generateString()); } @Generates Equivalence generateEquivalence() { return new Equivalence() { @Override protected boolean doEquivalent(T a, T b) { return false; } @Override protected int doHash(T t) { return 0; } final String string = paramString(Equivalence.class, generateInt()); @Override public String toString() { return string; } }; } @Generates CharMatcher generateCharMatcher() { return new CharMatcher() { @Override public boolean matches(char c) { return false; } final String string = paramString(CharMatcher.class, generateInt()); @Override public String toString() { return string; } }; } @Generates Ticker generateTicker() { return new Ticker() { @Override public long read() { return 0; } final String string = paramString(Ticker.class, generateInt()); @Override public String toString() { return string; } }; } // collect @Generates Comparator generateComparator() { return generateOrdering(); } @Generates Ordering generateOrdering() { return new Ordering() { @Override @SuppressWarnings("UnusedVariable") // intentionally weird Comparator public int compare(T left, T right) { return 0; } final String string = paramString(Ordering.class, generateInt()); @Override public String toString() { return string; } }; } @Empty static > Range generateRange() { return Range.all(); } @Generates static > Range generateRange(C freshElement) { return Range.singleton(freshElement); } @Generates static Iterable generateIterable(@Nullable E freshElement) { return generateList(freshElement); } @Generates static Collection generateCollection(@Nullable E freshElement) { return generateList(freshElement); } @Generates static List generateList(@Nullable E freshElement) { return generateArrayList(freshElement); } @Generates static ArrayList generateArrayList(@Nullable E freshElement) { ArrayList list = Lists.newArrayList(); list.add(freshElement); return list; } @Generates static LinkedList generateLinkedList(@Nullable E freshElement) { LinkedList list = Lists.newLinkedList(); list.add(freshElement); return list; } @Generates static ImmutableList generateImmutableList(E freshElement) { return ImmutableList.of(freshElement); } @Generates static ImmutableCollection generateImmutableCollection(E freshElement) { return generateImmutableList(freshElement); } @Generates static Set generateSet(@Nullable E freshElement) { return generateHashSet(freshElement); } @Generates static HashSet generateHashSet(@Nullable E freshElement) { return generateLinkedHashSet(freshElement); } @Generates static LinkedHashSet generateLinkedHashSet(@Nullable E freshElement) { LinkedHashSet set = Sets.newLinkedHashSet(); set.add(freshElement); return set; } @Generates static ImmutableSet generateImmutableSet(E freshElement) { return ImmutableSet.of(freshElement); } @Generates static > SortedSet generateSortedSet(E freshElement) { return generateNavigableSet(freshElement); } @Generates static > NavigableSet generateNavigableSet(E freshElement) { return generateTreeSet(freshElement); } @Generates static > TreeSet generateTreeSet(E freshElement) { TreeSet set = Sets.newTreeSet(); set.add(freshElement); return set; } @Generates static > ImmutableSortedSet generateImmutableSortedSet( E freshElement) { return ImmutableSortedSet.of(freshElement); } @Generates static Multiset generateMultiset(@Nullable E freshElement) { return generateHashMultiset(freshElement); } @Generates static HashMultiset generateHashMultiset(@Nullable E freshElement) { HashMultiset multiset = HashMultiset.create(); multiset.add(freshElement); return multiset; } @Generates static LinkedHashMultiset generateLinkedHashMultiset(@Nullable E freshElement) { LinkedHashMultiset multiset = LinkedHashMultiset.create(); multiset.add(freshElement); return multiset; } @Generates static ImmutableMultiset generateImmutableMultiset(E freshElement) { return ImmutableMultiset.of(freshElement); } @Generates static > SortedMultiset generateSortedMultiset(E freshElement) { return generateTreeMultiset(freshElement); } @Generates static > TreeMultiset generateTreeMultiset(E freshElement) { TreeMultiset multiset = TreeMultiset.create(); multiset.add(freshElement); return multiset; } @Generates static > ImmutableSortedMultiset generateImmutableSortedMultiset( E freshElement) { return ImmutableSortedMultiset.of(freshElement); } @Generates static Map generateMap(@Nullable K key, @Nullable V value) { return generateHashdMap(key, value); } @Generates static HashMap generateHashdMap(@Nullable K key, @Nullable V value) { return generateLinkedHashMap(key, value); } @Generates static LinkedHashMap generateLinkedHashMap(@Nullable K key, @Nullable V value) { LinkedHashMap map = Maps.newLinkedHashMap(); map.put(key, value); return map; } @Generates static ImmutableMap generateImmutableMap(K key, V value) { return ImmutableMap.of(key, value); } @Empty static ConcurrentMap generateConcurrentMap() { return Maps.newConcurrentMap(); } @Generates static ConcurrentMap generateConcurrentMap(K key, V value) { ConcurrentMap map = Maps.newConcurrentMap(); map.put(key, value); return map; } @Generates static , V> SortedMap generateSortedMap( K key, @Nullable V value) { return generateNavigableMap(key, value); } @Generates static , V> NavigableMap generateNavigableMap( K key, @Nullable V value) { return generateTreeMap(key, value); } @Generates static , V> TreeMap generateTreeMap( K key, @Nullable V value) { TreeMap map = Maps.newTreeMap(); map.put(key, value); return map; } @Generates static , V> ImmutableSortedMap generateImmutableSortedMap( K key, V value) { return ImmutableSortedMap.of(key, value); } @Generates static Multimap generateMultimap(@Nullable K key, @Nullable V value) { return generateListMultimap(key, value); } @Generates static ImmutableMultimap generateImmutableMultimap(K key, V value) { return ImmutableMultimap.of(key, value); } @Generates static ListMultimap generateListMultimap(@Nullable K key, @Nullable V value) { return generateArrayListMultimap(key, value); } @Generates static ArrayListMultimap generateArrayListMultimap( @Nullable K key, @Nullable V value) { ArrayListMultimap multimap = ArrayListMultimap.create(); multimap.put(key, value); return multimap; } @Generates static ImmutableListMultimap generateImmutableListMultimap(K key, V value) { return ImmutableListMultimap.of(key, value); } @Generates static SetMultimap generateSetMultimap(@Nullable K key, @Nullable V value) { return generateLinkedHashMultimap(key, value); } @Generates static HashMultimap generateHashMultimap(@Nullable K key, @Nullable V value) { HashMultimap multimap = HashMultimap.create(); multimap.put(key, value); return multimap; } @Generates static LinkedHashMultimap generateLinkedHashMultimap( @Nullable K key, @Nullable V value) { LinkedHashMultimap multimap = LinkedHashMultimap.create(); multimap.put(key, value); return multimap; } @Generates static ImmutableSetMultimap generateImmutableSetMultimap(K key, V value) { return ImmutableSetMultimap.of(key, value); } @Generates static BiMap generateBimap(@Nullable K key, @Nullable V value) { return generateHashBiMap(key, value); } @Generates static HashBiMap generateHashBiMap(@Nullable K key, @Nullable V value) { HashBiMap bimap = HashBiMap.create(); bimap.put(key, value); return bimap; } @Generates static ImmutableBiMap generateImmutableBimap(K key, V value) { return ImmutableBiMap.of(key, value); } @Generates static Table generateTable(R row, C column, V value) { return generateHashBasedTable(row, column, value); } @Generates static HashBasedTable generateHashBasedTable(R row, C column, V value) { HashBasedTable table = HashBasedTable.create(); table.put(row, column, value); return table; } @SuppressWarnings("rawtypes") // TreeBasedTable.create() is defined as such @Generates static RowSortedTable generateRowSortedTable(R row, C column, V value) { return generateTreeBasedTable(row, column, value); } @SuppressWarnings("rawtypes") // TreeBasedTable.create() is defined as such @Generates static TreeBasedTable generateTreeBasedTable(R row, C column, V value) { TreeBasedTable table = TreeBasedTable.create(); table.put(row, column, value); return table; } @Generates static ImmutableTable generateImmutableTable(R row, C column, V value) { return ImmutableTable.of(row, column, value); } // common.reflect @Generates TypeToken generateTypeToken() { return TypeToken.of(generateClass()); } // io types @Generates File generateFile() { return new File(generateString()); } @Generates static ByteArrayInputStream generateByteArrayInputStream() { return new ByteArrayInputStream(new byte[0]); } @Generates static InputStream generateInputStream() { return generateByteArrayInputStream(); } @Generates StringReader generateStringReader() { return new StringReader(generateString()); } @Generates Reader generateReader() { return generateStringReader(); } @Generates Readable generateReadable() { return generateReader(); } @Generates Buffer generateBuffer() { return generateCharBuffer(); } @Generates CharBuffer generateCharBuffer() { return CharBuffer.allocate(generateInt()); } @Generates ByteBuffer generateByteBuffer() { return ByteBuffer.allocate(generateInt()); } @Generates ShortBuffer generateShortBuffer() { return ShortBuffer.allocate(generateInt()); } @Generates IntBuffer generateIntBuffer() { return IntBuffer.allocate(generateInt()); } @Generates LongBuffer generateLongBuffer() { return LongBuffer.allocate(generateInt()); } @Generates FloatBuffer generateFloatBuffer() { return FloatBuffer.allocate(generateInt()); } @Generates DoubleBuffer generateDoubleBuffer() { return DoubleBuffer.allocate(generateInt()); } }