/* * 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 com.google.common.annotations.GwtIncompatible; import com.google.common.base.CharMatcher; import com.google.common.base.Charsets; import com.google.common.base.Equivalence; import com.google.common.base.Joiner; import com.google.common.base.Optional; 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.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.Nullable; /** * Generates fresh instances of types that are different from each other (if possible). * * @author Ben Yu */ @GwtIncompatible class FreshValueGenerator { private static final ImmutableMap<Class<?>, Method> GENERATORS; static { ImmutableMap.Builder<Class<?>, Method> builder = ImmutableMap.builder(); for (Method method : FreshValueGenerator.class.getDeclaredMethods()) { if (method.isAnnotationPresent(Generates.class)) { builder.put(method.getReturnType(), method); } } GENERATORS = builder.build(); } private static final ImmutableMap<Class<?>, Method> EMPTY_GENERATORS; static { ImmutableMap.Builder<Class<?>, Method> builder = ImmutableMap.builder(); for (Method method : FreshValueGenerator.class.getDeclaredMethods()) { if (method.isAnnotationPresent(Empty.class)) { builder.put(method.getReturnType(), method); } } EMPTY_GENERATORS = builder.build(); } private final AtomicInteger freshness = new AtomicInteger(1); private final ListMultimap<Class<?>, Object> sampleInstances = ArrayListMultimap.create(); /** * The freshness level at which the {@link Empty @Empty} annotated method was invoked to generate * instance. */ private final Map<Type, Integer> emptyInstanceGenerated = new HashMap<Type, Integer>(); final <T> void addSampleInstances(Class<T> type, Iterable<? extends T> instances) { sampleInstances.putAll(checkNotNull(type), checkNotNull(instances)); } /** * Returns a fresh instance for {@code type} if possible. The returned instance could be: * <ul> * <li>exactly of the given type, including generic type parameters, such as * {@code ImmutableList<String>}; * <li>of the raw type; * <li>null if no value can be generated. * </ul> */ @Nullable final Object generateFresh(TypeToken<?> type) { Object generated = generate(type); if (generated != null) { freshness.incrementAndGet(); } return generated; } @Nullable final <T> T generateFresh(Class<T> type) { return Primitives.wrap(type).cast(generateFresh(TypeToken.of(type))); } final <T> T newFreshProxy(final Class<T> 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 Object generate(TypeToken<?> type) { Class<?> rawType = type.getRawType(); List<Object> 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 = 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<Parameter> params = Invokable.from(generate).getParameters(); List<Object> 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 <T> T defaultGenerate(Class<T> rawType) { if (rawType.isInterface()) { // always create a new proxy return newProxy(rawType); } return ArbitraryInstances.get(rawType); } private <T> T newProxy(final Class<T> 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 protected Object handleInvocation(Object proxy, Method method, 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. */ Object interfaceMethodCalled(Class<?> interfaceType, Method method) { throw new UnsupportedOperationException(); } private <T> T pickInstance(T[] instances, T defaultValue) { return pickInstance(Arrays.asList(instances), defaultValue); } private <T> T pickInstance(Collection<T> 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<K, V>}, the method signature should be: * {@code Map<K, V> 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 private Class<?> generateClass() { return pickInstance( ImmutableList.of( int.class, long.class, void.class, Object.class, Object[].class, Iterable.class), Object.class); } @Generates private Object generateObject() { return generateString(); } @Generates private Number generateNumber() { return generateInt(); } @Generates private int generateInt() { return freshness.get(); } @Generates private Integer generateInteger() { return new Integer(generateInt()); } @Generates private long generateLong() { return generateInt(); } @Generates private Long generateLongObject() { return new Long(generateLong()); } @Generates private float generateFloat() { return generateInt(); } @Generates private Float generateFloatObject() { return new Float(generateFloat()); } @Generates private double generateDouble() { return generateInt(); } @Generates private Double generateDoubleObject() { return new Double(generateDouble()); } @Generates private short generateShort() { return (short) generateInt(); } @Generates private Short generateShortObject() { return new Short(generateShort()); } @Generates private byte generateByte() { return (byte) generateInt(); } @Generates private Byte generateByteObject() { return new Byte(generateByte()); } @Generates private char generateChar() { return generateString().charAt(0); } @Generates private Character generateCharacter() { return new Character(generateChar()); } @Generates private boolean generateBoolean() { return generateInt() % 2 == 0; } @Generates private Boolean generateBooleanObject() { return new Boolean(generateBoolean()); } @Generates private UnsignedInteger generateUnsignedInteger() { return UnsignedInteger.fromIntBits(generateInt()); } @Generates private UnsignedLong generateUnsignedLong() { return UnsignedLong.fromLongBits(generateLong()); } @Generates private BigInteger generateBigInteger() { return BigInteger.valueOf(generateInt()); } @Generates private BigDecimal generateBigDecimal() { return BigDecimal.valueOf(generateInt()); } @Generates private CharSequence generateCharSequence() { return generateString(); } @Generates private String generateString() { return Integer.toString(generateInt()); } @Generates private Comparable<?> generateComparable() { return generateString(); } @Generates private Pattern generatePattern() { return Pattern.compile(generateString()); } @Generates private Charset generateCharset() { return pickInstance(Charset.availableCharsets().values(), Charsets.UTF_8); } @Generates private Locale generateLocale() { return pickInstance(Locale.getAvailableLocales(), Locale.US); } @Generates private Currency generateCurrency() { try { Method method = Currency.class.getMethod("getAvailableCurrencies"); @SuppressWarnings("unchecked") // getAvailableCurrencies() returns Set<Currency>. Set<Currency> currencies = (Set<Currency>) method.invoke(null); return pickInstance(currencies, Currency.getInstance(Locale.US)); } catch (NoSuchMethodException notJava7) { return preJava7FreshCurrency(); } catch (InvocationTargetException cantCallGetAvailableCurrencies) { return preJava7FreshCurrency(); } catch (IllegalAccessException impossible) { throw new AssertionError(impossible); } } private Currency preJava7FreshCurrency() { for (Set<Locale> uselessLocales = Sets.newHashSet(); ; ) { Locale locale = generateLocale(); if (uselessLocales.contains(locale)) { // exhausted all locales return Currency.getInstance(Locale.US); } try { return Currency.getInstance(locale); } catch (IllegalArgumentException e) { uselessLocales.add(locale); } } } // common.base @Empty private <T> Optional<T> generateOptional() { return Optional.absent(); } @Generates private <T> Optional<T> generateOptional(T value) { return Optional.of(value); } @Generates private Joiner generateJoiner() { return Joiner.on(generateString()); } @Generates private Splitter generateSplitter() { return Splitter.on(generateString()); } @Generates private <T> Equivalence<T> generateEquivalence() { return new Equivalence<T>() { @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 private 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 private 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 private <T> Comparator<T> generateComparator() { return generateOrdering(); } @Generates private <T> Ordering<T> generateOrdering() { return new Ordering<T>() { @Override public int compare(T left, T right) { return 0; } final String string = paramString(Ordering.class, generateInt()); @Override public String toString() { return string; } }; } @Empty private static <C extends Comparable<?>> Range<C> generateRange() { return Range.all(); } @Generates private static <C extends Comparable<?>> Range<C> generateRange(C freshElement) { return Range.singleton(freshElement); } @Generates private static <E> Iterable<E> generateIterable(E freshElement) { return generateList(freshElement); } @Generates private static <E> Collection<E> generateCollection(E freshElement) { return generateList(freshElement); } @Generates private static <E> List<E> generateList(E freshElement) { return generateArrayList(freshElement); } @Generates private static <E> ArrayList<E> generateArrayList(E freshElement) { ArrayList<E> list = Lists.newArrayList(); list.add(freshElement); return list; } @Generates private static <E> LinkedList<E> generateLinkedList(E freshElement) { LinkedList<E> list = Lists.newLinkedList(); list.add(freshElement); return list; } @Generates private static <E> ImmutableList<E> generateImmutableList(E freshElement) { return ImmutableList.of(freshElement); } @Generates private static <E> ImmutableCollection<E> generateImmutableCollection(E freshElement) { return generateImmutableList(freshElement); } @Generates private static <E> Set<E> generateSet(E freshElement) { return generateHashSet(freshElement); } @Generates private static <E> HashSet<E> generateHashSet(E freshElement) { return generateLinkedHashSet(freshElement); } @Generates private static <E> LinkedHashSet<E> generateLinkedHashSet(E freshElement) { LinkedHashSet<E> set = Sets.newLinkedHashSet(); set.add(freshElement); return set; } @Generates private static <E> ImmutableSet<E> generateImmutableSet(E freshElement) { return ImmutableSet.of(freshElement); } @Generates private static <E extends Comparable<? super E>> SortedSet<E> generateSortedSet(E freshElement) { return generateNavigableSet(freshElement); } @Generates private static <E extends Comparable<? super E>> NavigableSet<E> generateNavigableSet(E freshElement) { return generateTreeSet(freshElement); } @Generates private static <E extends Comparable<? super E>> TreeSet<E> generateTreeSet( E freshElement) { TreeSet<E> set = Sets.newTreeSet(); set.add(freshElement); return set; } @Generates private static <E extends Comparable<? super E>> ImmutableSortedSet<E> generateImmutableSortedSet(E freshElement) { return ImmutableSortedSet.of(freshElement); } @Generates private static <E> Multiset<E> generateMultiset(E freshElement) { return generateHashMultiset(freshElement); } @Generates private static <E> HashMultiset<E> generateHashMultiset(E freshElement) { HashMultiset<E> multiset = HashMultiset.create(); multiset.add(freshElement); return multiset; } @Generates private static <E> LinkedHashMultiset<E> generateLinkedHashMultiset(E freshElement) { LinkedHashMultiset<E> multiset = LinkedHashMultiset.create(); multiset.add(freshElement); return multiset; } @Generates private static <E> ImmutableMultiset<E> generateImmutableMultiset(E freshElement) { return ImmutableMultiset.of(freshElement); } @Generates private static <E extends Comparable<E>> SortedMultiset<E> generateSortedMultiset( E freshElement) { return generateTreeMultiset(freshElement); } @Generates private static <E extends Comparable<E>> TreeMultiset<E> generateTreeMultiset( E freshElement) { TreeMultiset<E> multiset = TreeMultiset.create(); multiset.add(freshElement); return multiset; } @Generates private static <E extends Comparable<E>> ImmutableSortedMultiset<E> generateImmutableSortedMultiset(E freshElement) { return ImmutableSortedMultiset.of(freshElement); } @Generates private static <K, V> Map<K, V> generateMap(K key, V value) { return generateHashdMap(key, value); } @Generates private static <K, V> HashMap<K, V> generateHashdMap(K key, V value) { return generateLinkedHashMap(key, value); } @Generates private static <K, V> LinkedHashMap<K, V> generateLinkedHashMap(K key, V value) { LinkedHashMap<K, V> map = Maps.newLinkedHashMap(); map.put(key, value); return map; } @Generates private static <K, V> ImmutableMap<K, V> generateImmutableMap(K key, V value) { return ImmutableMap.of(key, value); } @Empty private static <K, V> ConcurrentMap<K, V> generateConcurrentMap() { return Maps.newConcurrentMap(); } @Generates private static <K, V> ConcurrentMap<K, V> generateConcurrentMap(K key, V value) { ConcurrentMap<K, V> map = Maps.newConcurrentMap(); map.put(key, value); return map; } @Generates private static <K extends Comparable<? super K>, V> SortedMap<K, V> generateSortedMap(K key, V value) { return generateNavigableMap(key, value); } @Generates private static <K extends Comparable<? super K>, V> NavigableMap<K, V> generateNavigableMap(K key, V value) { return generateTreeMap(key, value); } @Generates private static <K extends Comparable<? super K>, V> TreeMap<K, V> generateTreeMap( K key, V value) { TreeMap<K, V> map = Maps.newTreeMap(); map.put(key, value); return map; } @Generates private static <K extends Comparable<? super K>, V> ImmutableSortedMap<K, V> generateImmutableSortedMap(K key, V value) { return ImmutableSortedMap.of(key, value); } @Generates private static <K, V> Multimap<K, V> generateMultimap(K key, V value) { return generateListMultimap(key, value); } @Generates private static <K, V> ImmutableMultimap<K, V> generateImmutableMultimap( K key, V value) { return ImmutableMultimap.of(key, value); } @Generates private static <K, V> ListMultimap<K, V> generateListMultimap(K key, V value) { return generateArrayListMultimap(key, value); } @Generates private static <K, V> ArrayListMultimap<K, V> generateArrayListMultimap( K key, V value) { ArrayListMultimap<K, V> multimap = ArrayListMultimap.create(); multimap.put(key, value); return multimap; } @Generates private static <K, V> ImmutableListMultimap<K, V> generateImmutableListMultimap( K key, V value) { return ImmutableListMultimap.of(key, value); } @Generates private static <K, V> SetMultimap<K, V> generateSetMultimap(K key, V value) { return generateLinkedHashMultimap(key, value); } @Generates private static <K, V> HashMultimap<K, V> generateHashMultimap(K key, V value) { HashMultimap<K, V> multimap = HashMultimap.create(); multimap.put(key, value); return multimap; } @Generates private static <K, V> LinkedHashMultimap<K, V> generateLinkedHashMultimap( K key, V value) { LinkedHashMultimap<K, V> multimap = LinkedHashMultimap.create(); multimap.put(key, value); return multimap; } @Generates private static <K, V> ImmutableSetMultimap<K, V> generateImmutableSetMultimap( K key, V value) { return ImmutableSetMultimap.of(key, value); } @Generates private static <K, V> BiMap<K, V> generateBimap(K key, V value) { return generateHashBiMap(key, value); } @Generates private static <K, V> HashBiMap<K, V> generateHashBiMap(K key, V value) { HashBiMap<K, V> bimap = HashBiMap.create(); bimap.put(key, value); return bimap; } @Generates private static <K, V> ImmutableBiMap<K, V> generateImmutableBimap( K key, V value) { return ImmutableBiMap.of(key, value); } @Generates private static <R, C, V> Table<R, C, V> generateTable(R row, C column, V value) { return generateHashBasedTable(row, column, value); } @Generates private static <R, C, V> HashBasedTable<R, C, V> generateHashBasedTable( R row, C column, V value) { HashBasedTable<R, C, V> table = HashBasedTable.create(); table.put(row, column, value); return table; } @SuppressWarnings("rawtypes") // TreeBasedTable.create() is defined as such @Generates private static <R extends Comparable, C extends Comparable, V> RowSortedTable<R, C, V> generateRowSortedTable(R row, C column, V value) { return generateTreeBasedTable(row, column, value); } @SuppressWarnings("rawtypes") // TreeBasedTable.create() is defined as such @Generates private static <R extends Comparable, C extends Comparable, V> TreeBasedTable<R, C, V> generateTreeBasedTable(R row, C column, V value) { TreeBasedTable<R, C, V> table = TreeBasedTable.create(); table.put(row, column, value); return table; } @Generates private static <R, C, V> ImmutableTable<R, C, V> generateImmutableTable( R row, C column, V value) { return ImmutableTable.of(row, column, value); } // common.reflect @Generates private TypeToken<?> generateTypeToken() { return TypeToken.of(generateClass()); } // io types @Generates private File generateFile() { return new File(generateString()); } @Generates private static ByteArrayInputStream generateByteArrayInputStream() { return new ByteArrayInputStream(new byte[0]); } @Generates private static InputStream generateInputStream() { return generateByteArrayInputStream(); } @Generates private StringReader generateStringReader() { return new StringReader(generateString()); } @Generates private Reader generateReader() { return generateStringReader(); } @Generates private Readable generateReadable() { return generateReader(); } @Generates private Buffer generateBuffer() { return generateCharBuffer(); } @Generates private CharBuffer generateCharBuffer() { return CharBuffer.allocate(generateInt()); } @Generates private ByteBuffer generateByteBuffer() { return ByteBuffer.allocate(generateInt()); } @Generates private ShortBuffer generateShortBuffer() { return ShortBuffer.allocate(generateInt()); } @Generates private IntBuffer generateIntBuffer() { return IntBuffer.allocate(generateInt()); } @Generates private LongBuffer generateLongBuffer() { return LongBuffer.allocate(generateInt()); } @Generates private FloatBuffer generateFloatBuffer() { return FloatBuffer.allocate(generateInt()); } @Generates private DoubleBuffer generateDoubleBuffer() { return DoubleBuffer.allocate(generateInt()); } }