package memorymeasurer;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ConcurrentHashMultiset;
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.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.ImmutableSortedSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.LinkedHashMultiset;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.collect.Table;
import com.google.common.collect.TreeBasedTable;
import com.google.common.collect.TreeMultimap;
import com.google.common.collect.TreeMultiset;
import java.lang.reflect.Constructor;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import objectexplorer.MemoryMeasurer;
import objectexplorer.ObjectGraphMeasurer;
import objectexplorer.ObjectGraphMeasurer.Footprint;

public class ElementCostOfDataStructures {
  public static void main(String[] args) throws Exception {
    caption(String.format("    %2s-bit architecture   ", System.getProperty("sun.arch.data.model")));
    caption("  Basic Lists, Sets, Maps ");

    analyze(new CollectionPopulator(defaultSupplierFor(ArrayList.class)));
    analyze(new ImmutableListPopulator());

    analyze(new CollectionPopulator(defaultSupplierFor(HashSet.class)));
    analyze(new ImmutableSetPopulator());

    analyze(new CollectionPopulator(defaultSupplierFor(TreeSet.class), EntryFactories.COMPARABLE));
    analyze(new ImmutableSortedSetPopulator());

    analyze(new MapPopulator(defaultSupplierFor(HashMap.class)));
    analyze(new ImmutableMapPopulator());
    analyze(new MapPopulator(defaultSupplierFor(LinkedHashMap.class)));

    analyze(new MapPopulator(defaultSupplierFor(TreeMap.class), EntryFactories.COMPARABLE));
    analyze(new ImmutableSortedMapPopulator());

    caption("ConcurrentHashMap/MapMaker");

    analyze(new MapPopulator(defaultSupplierFor(ConcurrentHashMap.class)));
    analyzeMapMaker("MapMaker", new Supplier<MapMaker>() { public MapMaker get() { return
      new MapMaker(); } });
    analyze("MapMaker_Expires", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().expiration(5, TimeUnit.DAYS).makeMap(); } }));
    analyze("MapMaker_Evicts", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().maximumSize(1000000).makeMap(); } }));
    analyze("MapMaker_Expires_Evicts", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().maximumSize(1000000).expiration(3, TimeUnit.DAYS).makeMap(); } }));
    analyze("MapMaker_SoftKeys", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().softKeys().makeMap(); } }));
    analyze("MapMaker_SoftValues", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().softValues().makeMap(); } }));
    analyze("MapMaker_SoftKeysValues", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().softKeys().softValues().makeMap(); } }));
    analyze("MapMaker_Evicts_SoftKeys", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().maximumSize(1000000).softKeys().makeMap(); } }));
    analyze("MapMaker_Evicts_SoftValues", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().maximumSize(1000000).softValues().makeMap(); } }));
    analyze("MapMaker_Evicts_SoftKeysValues", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().maximumSize(1000000).softKeys().softValues().makeMap(); } }));
    analyze("MapMaker_Expires_SoftKeys", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().expiration(3, TimeUnit.DAYS).softKeys().makeMap(); } }));
    analyze("MapMaker_Expires_SoftValues", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().expiration(3, TimeUnit.DAYS).softValues().makeMap(); } }));
    analyze("MapMaker_Expires_SoftKeysValues", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().expiration(3, TimeUnit.DAYS).softKeys().softValues().makeMap(); } }));
    analyze("MapMaker_Expires_Evicts_SoftKeys", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().maximumSize(1000000).expiration(3, TimeUnit.DAYS).softKeys().makeMap(); } }));
    analyze("MapMaker_Expires_Evicts_SoftValues", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().maximumSize(1000000).expiration(3, TimeUnit.DAYS).softValues().makeMap(); } }));
    analyze("MapMaker_Expires_Evicts_SoftKeysValues", new MapPopulator(new Supplier<Map>() { public Map get() { return
      new MapMaker().maximumSize(1000000).expiration(3, TimeUnit.DAYS).softKeys().softValues().makeMap(); } }));

    caption("        Multisets         ");

    analyze("HashMultiset_Worst", new MultisetPopulator_Worst(new Supplier<Multiset>() { public Multiset get() { return
      HashMultiset.create(); } }));
    analyze("LinkedHashMultiset_Worst", new MultisetPopulator_Worst(new Supplier<Multiset>() { public Multiset get() { return
      LinkedHashMultiset.create(); } }));
    analyze("TreeMultiset_Worst", new MultisetPopulator_Worst(new Supplier<Multiset>() { public Multiset get() { return
      TreeMultiset.create(); } }, EntryFactories.COMPARABLE));
    analyze("ConcurrentHashMultiset_Worst", new MultisetPopulator_Worst(new Supplier<Multiset>() { public Multiset get() { return
      ConcurrentHashMultiset.create(); } }));

    System.out.println();

    analyze("HashMultiset_Best ", new MultisetPopulator_Best(new Supplier<Multiset>() { public Multiset get() { return
      HashMultiset.create(); } }));
    analyze("LinkedHashMultiset_Best ", new MultisetPopulator_Best(new Supplier<Multiset>() { public Multiset get() { return
      LinkedHashMultiset.create(); } }));
    analyze("TreeMultiset_Best ", new MultisetPopulator_Best(new Supplier<Multiset>() { public Multiset get() { return
      TreeMultiset.create(); } }, EntryFactories.COMPARABLE));
    analyze("ConcurrentHashMultiset_Best ", new MultisetPopulator_Best(new Supplier<Multiset>() { public Multiset get() { return
      ConcurrentHashMultiset.create(); } }));

    caption("        Multimaps         ");

    analyze("HashMultimap_Worst", new MultimapPopulator_Worst(new Supplier<Multimap>() { public Multimap get() { return
      HashMultimap.create(); } }));
    analyze("LinkedHashMultimap_Worst", new MultimapPopulator_Worst(new Supplier<Multimap>() { public Multimap get() { return
      LinkedHashMultimap.create(); } }));
    analyze("TreeMultimap_Worst", new MultimapPopulator_Worst(new Supplier<Multimap>() { public Multimap get() { return
      TreeMultimap.create(); } }, EntryFactories.COMPARABLE));
    analyze("ArrayListMultimap_Worst", new MultimapPopulator_Worst(new Supplier<Multimap>() { public Multimap get() { return
      ArrayListMultimap.create(); } }));
    analyze("LinkedListMultimap_Worst", new MultimapPopulator_Worst(new Supplier<Multimap>() { public Multimap get() { return
      LinkedListMultimap.create(); } }));
    analyze(new ImmutableMultimapPopulator_Worst());
    analyze(new ImmutableListMultimapPopulator_Worst());
    analyze(new ImmutableSetMultimapPopulator_Worst());

    System.out.println();

    analyze("HashMultimap_Best ", new MultimapPopulator_Best(new Supplier<Multimap>() { public Multimap get() { return
      HashMultimap.create(); } }));
    analyze("LinkedHashMultimap_Best ", new MultimapPopulator_Best(new Supplier<Multimap>() { public Multimap get() { return
      LinkedHashMultimap.create(); } }));
    analyze("TreeMultimap_Best ", new MultimapPopulator_Best(new Supplier<Multimap>() { public Multimap get() { return
      TreeMultimap.create(); } }, EntryFactories.COMPARABLE));
    analyze("ArrayListMultimap_Best ", new MultimapPopulator_Best(new Supplier<Multimap>() { public Multimap get() { return
      ArrayListMultimap.create(); } }));
    analyze("LinkedListMultimap_Best ", new MultimapPopulator_Best(new Supplier<Multimap>() { public Multimap get() { return
      LinkedListMultimap.create(); } }));
    analyze(new ImmutableMultimapPopulator_Best());
    analyze(new ImmutableListMultimapPopulator_Best());
    analyze(new ImmutableSetMultimapPopulator_Best());

    caption("          Tables          ");

    analyze("HashBasedTable", new TablePopulator_Worst(new Supplier<Table>() { public Table get() { return
      HashBasedTable.create(); } } ));
    analyze("TreeBasedTable", new TablePopulator_Worst(new Supplier<Table>() { public Table get() { return
      TreeBasedTable.create(); } }, EntryFactories.COMPARABLE));

    caption("          BiMaps          ");

    analyze("HashBiMap", new MapPopulator(new Supplier<Map>() { public Map get() { return
      HashBiMap.create(); } }));
    analyze(new ImmutableBiMapPopulator());

    caption("           Misc           ");

    analyze(new MapPopulator(defaultSupplierFor(WeakHashMap.class)));
    analyze(new CollectionPopulator(defaultSupplierFor(LinkedList.class)));
    analyze(new CollectionPopulator(defaultSupplierFor(ArrayDeque.class)));
    analyze(new CollectionPopulator(defaultSupplierFor(LinkedHashSet.class)));
    analyze(new CollectionPopulator(defaultSupplierFor(PriorityQueue.class), EntryFactories.COMPARABLE));
    analyze(new CollectionPopulator(defaultSupplierFor(PriorityBlockingQueue.class), EntryFactories.COMPARABLE));
    analyze(new CollectionPopulator(defaultSupplierFor(ConcurrentSkipListSet.class), EntryFactories.COMPARABLE));
    analyze(new CollectionPopulator(defaultSupplierFor(CopyOnWriteArrayList.class)));
    analyze(new CollectionPopulator(defaultSupplierFor(CopyOnWriteArraySet.class)));
    analyze(new CollectionPopulator(defaultSupplierFor(DelayQueue.class), EntryFactories.DELAYED));
    analyze(new CollectionPopulator(defaultSupplierFor(LinkedBlockingQueue.class)));
    analyze(new CollectionPopulator(defaultSupplierFor(LinkedBlockingDeque.class)));

    caption("  Synchronization Structures");

    analyzeOneOff("ReentrantLock", new ReentrantLock(true));
    analyzeOneOff("Semaphore", new Semaphore(1, true));
    analyzeOneOff("ReadWriteLock", new ReentrantReadWriteLock(true));
  }

  private static void caption(String caption) {
    System.out.println();
    System.out.println("========================================== " + caption
        + " ==========================================");
    System.out.println();
  }

  private static void analyzeMapMaker(String caption, Supplier<MapMaker> supplier) {
    analyze(caption, new MapPopulator(new MapSupplier(supplier)));
    analyze(caption + "_Computing", new MapPopulator(new ComputingMapSupplier(supplier)));
  }

  private static class MapSupplier implements Supplier<Map> {
    private final Supplier<MapMaker> mapMakerSupplier;
    MapSupplier(Supplier<MapMaker> mapMakerSupplier) {
      this.mapMakerSupplier = mapMakerSupplier;
    }

    public Map get() {
      return mapMakerSupplier.get().makeMap();
    }
  }

  private static class ComputingMapSupplier implements Supplier<Map> {
    private final Supplier<MapMaker> mapMakerSupplier;
    ComputingMapSupplier(Supplier<MapMaker> mapMakerSupplier) {
      this.mapMakerSupplier = mapMakerSupplier;
    }

    public Map get() {
      return mapMakerSupplier.get().makeComputingMap(new Function() {
        public Object apply(Object o) {
          return o;
        }
      });
    }
  }

  static void analyze(Populator<?> populator) {
    analyze(populator.toString(), populator);
  }

  static void analyze(String caption, Populator<?> populator) {
    AvgEntryCost cost = averageEntryCost(populator, 16, 256 * 31);
    System.out.printf("%40s :: Bytes = %6.2f, Objects = %5.2f Refs = %5.2f Primitives = %s%n",
        caption, cost.bytes, cost.objects, cost.refs, cost.primitives);
  }

  static void analyzeOneOff(String caption, Object o) {
    Footprint footprint = ObjectGraphMeasurer.measure(o);
    long bytes = MemoryMeasurer.measureBytes(o);
    System.out.printf("%40s :: Bytes = %6d, Objects = %5d Refs = %5d Primitives = %s%n",
        caption, bytes, footprint.getObjects(), footprint.getReferences(), footprint.getPrimitives());
  }

  static AvgEntryCost averageEntryCost(Populator<?> populator, int initialEntries, int entriesToAdd) {
    Preconditions.checkArgument(initialEntries >= 0, "initialEntries negative");
    Preconditions.checkArgument(entriesToAdd > 0, "entriesToAdd negative or zero");

    Predicate<Object> predicate = Predicates.not(Predicates.instanceOf(
        populator.getEntryType()));

    Object collection1 = populator.construct(initialEntries);
    Footprint footprint1 = ObjectGraphMeasurer.measure(collection1, predicate);
    long bytes1 = MemoryMeasurer.measureBytes(collection1, predicate);

    Object collection2 = populator.construct(initialEntries + entriesToAdd);
    Footprint footprint2 = ObjectGraphMeasurer.measure(collection2, predicate);
    long bytes2 = MemoryMeasurer.measureBytes(collection2, predicate);

    double objects = (footprint2.getObjects() - footprint1.getObjects()) / (double) entriesToAdd;
    double refs = (footprint2.getReferences() - footprint1.getReferences()) / (double) entriesToAdd;
    double bytes = (bytes2 - bytes1) / (double)entriesToAdd;

    Map<Class<?>, Double> primitives = Maps.newHashMap();
    for (Class<?> primitiveType : primitiveTypes) {
      int initial = footprint1.getPrimitives().count(primitiveType);
      int ending = footprint2.getPrimitives().count(primitiveType);
      if (initial != ending) {
        primitives.put(primitiveType, (ending - initial) / (double) entriesToAdd);
      }
    }

    return new AvgEntryCost(objects, refs, primitives, bytes);
  }

  private static final ImmutableSet<Class<?>> primitiveTypes = ImmutableSet.<Class<?>>of(
      boolean.class, byte.class, char.class, short.class,
      int.class, float.class, long.class, double.class);

  private static class AvgEntryCost {
    final double objects;
    final double refs;
    final ImmutableMap<Class<?>, Double> primitives;
    final double bytes;
    AvgEntryCost(double objects, double refs, Map<Class<?>, Double> primitives, double bytes) {
      this.objects = objects;
      this.refs = refs;
      this.primitives = ImmutableMap.copyOf(primitives);
      this.bytes = bytes;
    }
  }

  private static class DefaultConstructorSupplier<C> implements Supplier<C> {
    private final Constructor<C> constructor;
    DefaultConstructorSupplier(Class<C> clazz) throws NoSuchMethodException {
      this.constructor = clazz.getConstructor();
    }
    public C get() {
      try {
        return constructor.newInstance();
      } catch (Exception e) {
        throw Throwables.propagate(e);
      }
    }

    @Override
    public String toString() {
      return constructor.getDeclaringClass().getSimpleName();
    }
  }

  static <C> DefaultConstructorSupplier<C> defaultSupplierFor(Class<C> clazz) throws NoSuchMethodException {
    return new DefaultConstructorSupplier(clazz);
  }
}

interface Populator<C> {
  Class<?> getEntryType();

  C construct(int entries);
}

abstract class AbstractPopulator<C> implements Populator<C> {
  private final EntryFactory entryFactory;
  AbstractPopulator() { this(EntryFactories.REGULAR); }
  AbstractPopulator(EntryFactory entryFactory) {
    this.entryFactory = entryFactory;
  }

  protected Object newEntry() {
    return entryFactory.get();
  }

  public Class<?> getEntryType() {
    return entryFactory.getEntryType();
  }
}

abstract class MutablePopulator<C> extends AbstractPopulator<C> {
  private final Supplier<? extends C> factory;

  MutablePopulator(Supplier<? extends C> factory) {
    this(factory, EntryFactories.REGULAR);
  }

  MutablePopulator(Supplier<? extends C> factory, EntryFactory entryFactory) {
    super(entryFactory);
    this.factory = factory;
  }

  protected abstract void addEntry(C target);

  public C construct(int entries) {
    C collection = factory.get();
    for (int i = 0; i < entries; i++) {
      addEntry(collection);
    }
    return collection;
  }

  @Override
  public String toString() {
    return factory.toString();
  }
}

class MapPopulator extends MutablePopulator<Map> {
  MapPopulator(Supplier<? extends Map> mapFactory) {
    super(mapFactory);
  }

  MapPopulator(Supplier<? extends Map> mapFactory, EntryFactory entryFactory) {
    super(mapFactory, entryFactory);
  }

  public void addEntry(Map map) {
    map.put(newEntry(), newEntry());
  }
}

class CollectionPopulator extends MutablePopulator<Collection> {
  CollectionPopulator(Supplier<? extends Collection> collectionFactory) {
    super(collectionFactory);
  }

  CollectionPopulator(Supplier<? extends Collection> collectionFactory, EntryFactory entryFactory) {
    super(collectionFactory, entryFactory);
  }

  public void addEntry(Collection collection) {
    collection.add(newEntry());
  }
}

class MultimapPopulator_Worst extends MutablePopulator<Multimap> {
  MultimapPopulator_Worst(Supplier<? extends Multimap> multimapFactory) {
    super(multimapFactory);
  }
  MultimapPopulator_Worst(Supplier<? extends Multimap> multimapFactory, EntryFactory entryFactory) {
    super(multimapFactory, entryFactory);
  }

  public void addEntry(Multimap multimap) {
    multimap.put(newEntry(), newEntry());
  }
}

class MultimapPopulator_Best extends MutablePopulator<Multimap> {
  MultimapPopulator_Best(Supplier<? extends Multimap> multimapFactory) {
    super(multimapFactory);
  }
  MultimapPopulator_Best(Supplier<? extends Multimap> multimapFactory, EntryFactory entryFactory) {
    super(multimapFactory, entryFactory);
  }

  private final Object key = newEntry();
  public void addEntry(Multimap multimap) {
    multimap.put(key, newEntry());
  }
}

class MultisetPopulator_Worst extends MutablePopulator<Multiset> {
  MultisetPopulator_Worst(Supplier<? extends Multiset> multisetFactory) {
    super(multisetFactory);
  }
  MultisetPopulator_Worst(Supplier<? extends Multiset> multisetFactory, EntryFactory entryFactory) {
    super(multisetFactory, entryFactory);
  }

  public void addEntry(Multiset multiset) {
    multiset.add(newEntry());
  }
}

class MultisetPopulator_Best extends MutablePopulator<Multiset> {
  MultisetPopulator_Best(Supplier<? extends Multiset> multisetFactory) {
    super(multisetFactory);
  }
  MultisetPopulator_Best(Supplier<? extends Multiset> multisetFactory, EntryFactory entryFactory) {
    super(multisetFactory, entryFactory);
  }

  private final Object key = newEntry();
  public void addEntry(Multiset multiset) {
    multiset.add(key);
  }
}

class TablePopulator_Worst extends MutablePopulator<Table> {
  TablePopulator_Worst(Supplier<? extends Table> tableFactory) {
    super(tableFactory);
  }
  TablePopulator_Worst(Supplier<? extends Table> tableFactory, EntryFactory entryFactory) {
    super(tableFactory, entryFactory);
  }

  public void addEntry(Table table) {
    table.put(newEntry(), newEntry(), newEntry());
  }
}

/** Immutable classes */

class ImmutableListPopulator extends AbstractPopulator<ImmutableList> {
  public ImmutableList construct(int entries) {
    ImmutableList.Builder builder = ImmutableList.builder();
    for (int i = 0; i < entries; i++) {
      builder.add(newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableList";
  }
}

class ImmutableSetPopulator extends AbstractPopulator<ImmutableSet> {
  public ImmutableSet construct(int entries) {
    ImmutableSet.Builder builder = ImmutableSet.builder();
    for (int i = 0; i < entries; i++) {
      builder.add(newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableSet";
  }
}

class ImmutableMapPopulator extends AbstractPopulator<ImmutableMap> {
  public ImmutableMap construct(int entries) {
    ImmutableMap.Builder builder = ImmutableMap.builder();
    for (int i = 0; i < entries; i++) {
      builder.put(newEntry(), newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableMap";
  }
}

class ImmutableSortedSetPopulator extends AbstractPopulator<ImmutableSortedSet> {
  ImmutableSortedSetPopulator() {
    super(EntryFactories.COMPARABLE);
  }

  public ImmutableSortedSet construct(int entries) {
    ImmutableSortedSet.Builder builder = ImmutableSortedSet.<Comparable>naturalOrder();
    for (int i = 0; i < entries; i++) {
      builder.add(newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableSortedSet";
  }
}

class ImmutableSortedMapPopulator extends AbstractPopulator<ImmutableSortedMap> {
  ImmutableSortedMapPopulator() {
    super(EntryFactories.COMPARABLE);
  }

  public ImmutableSortedMap construct(int entries) {
    ImmutableSortedMap.Builder builder = ImmutableSortedMap.<Comparable, Object>naturalOrder();
    for (int i = 0; i < entries; i++) {
      builder.put(newEntry(), newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableSortedMap";
  }
}

class ImmutableBiMapPopulator extends AbstractPopulator<ImmutableBiMap> {
  public ImmutableBiMap construct(int entries) {
    ImmutableBiMap.Builder builder = ImmutableBiMap.builder();
    for (int i = 0; i < entries; i++) {
      builder.put(newEntry(), newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableBiMap";
  }
}

class ImmutableMultimapPopulator_Worst extends AbstractPopulator<ImmutableMultimap> {
  public ImmutableMultimap construct(int entries) {
    ImmutableMultimap.Builder builder = ImmutableMultimap.builder();
    for (int i = 0; i < entries; i++) {
      builder.put(newEntry(), newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableMultimap_Worst";
  }
}

class ImmutableMultimapPopulator_Best extends AbstractPopulator<ImmutableMultimap> {
  public ImmutableMultimap construct(int entries) {
    ImmutableMultimap.Builder builder = ImmutableMultimap.builder();
    Object key = newEntry();
    for (int i = 0; i < entries; i++) {
      builder.put(key, newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableMultimap_Best ";
  }
}

class ImmutableListMultimapPopulator_Worst extends AbstractPopulator<ImmutableListMultimap> {
  public ImmutableListMultimap construct(int entries) {
    ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder();
    for (int i = 0; i < entries; i++) {
      builder.put(newEntry(), newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableListMultimap_Worst";
  }
}

class ImmutableListMultimapPopulator_Best extends AbstractPopulator<ImmutableListMultimap> {
  public ImmutableListMultimap construct(int entries) {
    ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder();
    Object key = newEntry();
    for (int i = 0; i < entries; i++) {
      builder.put(key, newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableListMultimap_Best ";
  }
}

class ImmutableSetMultimapPopulator_Worst extends AbstractPopulator<ImmutableSetMultimap> {
  public ImmutableSetMultimap construct(int entries) {
    ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder();
    for (int i = 0; i < entries; i++) {
      builder.put(newEntry(), newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableSetMultimap_Worst";
  }
}

class ImmutableSetMultimapPopulator_Best extends AbstractPopulator<ImmutableSetMultimap> {
  public ImmutableSetMultimap construct(int entries) {
    ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder();
    Object key = newEntry();
    for (int i = 0; i < entries; i++) {
      builder.put(key, newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableSetMultimap_Best ";
  }
}

class ImmutableMultisetPopulator_Worst extends AbstractPopulator<ImmutableMultiset> {
  public ImmutableMultiset construct(int entries) {
    ImmutableMultiset.Builder builder = ImmutableMultiset.builder();
    for (int i = 0; i < entries; i++) {
      builder.add(newEntry());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableMultiset_Worst";
  }
}

class ImmutableMultisetPopulator_Best extends AbstractPopulator<ImmutableMultiset> {
  public ImmutableMultiset construct(int entries) {
    ImmutableMultiset.Builder builder = ImmutableMultiset.builder();
    Object key = newEntry();
    for (int i = 0; i < entries; i++) {
      builder.add(key);
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "ImmutableMultiset_Best ";
  }
}

interface EntryFactory extends Supplier {
  Class<?> getEntryType();
}

enum EntryFactories implements EntryFactory {
  REGULAR {
    public Class<?> getEntryType() { return Element.class; }
    public Object get() { return new Element(); }
  },
  COMPARABLE {
    public Class<?> getEntryType() { return ComparableElement.class; }
    public Object get() { return new ComparableElement(); }
  },
  DELAYED {
    public Class<?> getEntryType() { return DelayedElement.class; }
    public Object get() { return new DelayedElement(); }
  };
}

class Element { }

class ComparableElement extends Element implements Comparable {
  public int compareTo(Object o) { if (this == o) return 0; else return 1; }
}

class DelayedElement extends Element implements Delayed {
  public long getDelay(TimeUnit unit) { return 0; }
  public int compareTo(Delayed o) { if (this == o) return 0; else return 1; }
}