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

com.bbn.bue.common.collections.MapUtils Maven / Gradle / Ivy

The newest version!
package com.bbn.bue.common.collections;

import com.bbn.bue.common.StringUtils;
import com.bbn.bue.common.collections.IterableUtils.ZipPair;

import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;

import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import static com.google.common.base.Preconditions.checkNotNull;

public final class MapUtils {

  private MapUtils() {
    throw new UnsupportedOperationException();
  }

  /**
   * Pairs up the values of a map by their common keys.
   */
  public static  PairedMapValues zipValues(final Map left, final Map right) {
    checkNotNull(left);
    checkNotNull(right);
    final ImmutableList.Builder> pairedValues =
        ImmutableList.builder();
    final ImmutableList.Builder leftOnly = ImmutableList.builder();
    final ImmutableList.Builder rightOnly = ImmutableList.builder();

    for (final Map.Entry leftEntry : left.entrySet()) {
      final K key = leftEntry.getKey();
      if (right.containsKey(key)) {
        pairedValues.add(ZipPair.from(leftEntry.getValue(), right.get(key)));
      } else {
        leftOnly.add(leftEntry.getValue());
      }
    }

    for (final Map.Entry rightEntry : right.entrySet()) {
      if (!left.containsKey(rightEntry.getKey())) {
        rightOnly.add(rightEntry.getValue());
      }
    }

    return new PairedMapValues(pairedValues.build(), leftOnly.build(),
        rightOnly.build());
  }

  /**
   * Return a copy of the input map with keys transformed by {@code keyInjection} and values
   * transformed by {@code valueFunction}. Beware: {@code keyInjection} must be an injection over
   * all the keys of the input map. If two original keys are mapped to the same value, an {@link
   * java.lang.IllegalArgumentException} will be returned.
   *
   * Neither {@code keyInjection} nor {@code valueFunction} may return null. If one does, an
   * exception will be thrown.
   */
  public static  ImmutableMap copyWithTransformedEntries(Map input,
      Function keyInjection, Function valueFunction) {
    final ImmutableMap.Builder ret = ImmutableMap.builder();

    for (final Map.Entry entry : input.entrySet()) {
      ret.put(keyInjection.apply(entry.getKey()), valueFunction.apply(entry.getValue()));
    }
    return ret.build();
  }

  public static class PairedMapValues {

    public PairedMapValues(final List> pairedValues, final List leftOnly,
        final List rightOnly) {
      this.pairedValues = ImmutableList.copyOf(pairedValues);
      this.leftOnly = ImmutableList.copyOf(leftOnly);
      this.rightOnly = ImmutableList.copyOf(rightOnly);
    }

    public List> pairedValues() {
      return pairedValues;
    }

    public List leftOnly() {
      return leftOnly;
    }

    public List rightOnly() {
      return rightOnly;
    }

    public boolean perfectlyAligned() {
      return leftOnly.isEmpty() && rightOnly.isEmpty();
    }

    private final List> pairedValues;
    private final List leftOnly;
    private final List rightOnly;
  }

  public static  ImmutableSet allKeys(final Iterable> maps) {
    final ImmutableSet.Builder builder = ImmutableSet.builder();

    for (final Map map : maps) {
      builder.addAll(map.keySet());
    }

    return builder.build();
  }

  public static  Function, V> getEntryValue() {
    return new Function, V>() {
      @Override
      public V apply(final Map.Entry entry) {
        return entry.getValue();
      }
    };
  }

  public static  Function, K> getEntryKey() {
    return new Function, K>() {
      @Override
      public K apply(final Map.Entry entry) {
        return entry.getKey();
      }
    };
  }

  public static  Function, Iterable> getValues() {
    return new Function, Iterable>() {
      @Override
      public Iterable apply(final Map input) {
        return input.values();
      }
    };
  }

  public static > Ordering> byValueOrderingAscending() {
    return Ordering.natural().onResultOf(MapUtils.getEntryValue());
  }

  public static > Ordering> byValueOrderingDescending() {
    return Ordering.natural().onResultOf(MapUtils.getEntryValue()).reverse();
  }

  public static  Ordering> byValueOrdering(final Ordering valueOrdering) {
    return valueOrdering.onResultOf(MapUtils.getEntryValue());
  }

  public static  List> sortedCopyOfEntries(final Map map,
      final Ordering> ordering) {
    return ordering.sortedCopy(map.entrySet());
  }

  /**
   * Returns an immutable copy of the given map whose iteration order has keys sorted by the key
   * type's natural ordering. No map keys or values may be {@code null}.
   */
  public static , V> ImmutableMap copyWithSortedKeys(
      final Map map) {
    return copyWithKeysSortedBy(map, Ordering.natural());
  }

  /**
   * Returns an immutable copy of the given map whose iteration order has keys sorted by the given
   * {@link Ordering}. No map keys or values may be {@code null}.
   */
  public static  ImmutableMap copyWithKeysSortedBy(final Map map,
      final Ordering ordering) {
    final ImmutableMap.Builder ret = ImmutableMap.builder();
    for (final K key : ordering.sortedCopy(map.keySet())) {
      ret.put(key, map.get(key));
    }
    return ret.build();
  }

  public static , V> Ordering> byKeyDescendingOrdering() {
    return Ordering.natural().onResultOf(MapUtils.getEntryKey());
  }

  public static  Ordering> byKeyOrdering(Ordering keyOrdering) {
    return keyOrdering.onResultOf(MapUtils.getEntryKey());
  }

  /**
   * Returns a new map which is contains all the mappings in both provided maps. This method will
   * throw an exception if there is any key {@code K} such that {@code K} is a key in both maps and
   * {@code !first.get(K).equals(second.get(K))}.    The iteration order of the resulting map will
   * contain first all entries in the first map in the order provided by {@code first.entrySet()}
   * followed by all non-duplicate entries in the second map in the order provided by {@code
   * second.entrySet()}.  This method is null-intolerant: if either input map contains {@code null},
   * an exception will be thrown.
   */
  public static  ImmutableMap disjointUnion(Map first, Map second) {
    checkNotNull(first);
    checkNotNull(second);
    final ImmutableMap.Builder ret = ImmutableMap.builder();

    // will throw an exception if there is any null mapping
    ret.putAll(first);
    for (final Entry secondEntry : second.entrySet()) {
      final V firstForKey = first.get(secondEntry.getKey());
      if (firstForKey == null) {
        // non-duplicate
        // we know the first map doesn't actually map this key to null
        // or ret.putAll(first) above would fail
        ret.put(secondEntry.getKey(), secondEntry.getValue());
      } else if (firstForKey.equals(secondEntry.getValue())) {
        // duplicate. This is okay. Do nothing.
      } else {
        throw new RuntimeException("When attempting a disjoint map union, " +
            String.format("for key %s, first map had %s but second had %s", secondEntry.getKey(),
                firstForKey, secondEntry.getValue()));
      }
    }

    return ret.build();
  }

  /**
   * Creates a copy of the supplied map with its keys transformed by the supplied function, which
   * must be one-to-one on the keys of the original map. If two keys are mapped to the same value by
   * {@code injection}, an {@code IllegalArgumentException} is thrown.
   */
  public static  ImmutableMap copyWithKeysTransformedByInjection(
      final Map map, final Function injection) {
    final ImmutableMap.Builder ret = ImmutableMap.builder();
    for (final Map.Entry entry : map.entrySet()) {
      ret.put(injection.apply(entry.getKey()), entry.getValue());
    }
    return ret.build();
  }

  /**
   * Returns the length of the longest key in a map, or 0 if the map is empty. Useful for printing
   * tables, etc. The map may not have any null keys.
   */
  public static  int longestKeyLength(Map map) {
    if (map.isEmpty()) {
      return 0;
    }

    return Ordering.natural().max(
        FluentIterable.from(map.keySet())
            .transform(StringUtils.ToLength));
  }

  /**
   * Just like {@link com.google.common.base.Functions#forMap(Map, Object)} except it allows a
   * potentially non-constant {@link Function} as the default.
   */
  public static  Function asFunction(Map map,
      Function defaultFunction) {
    return new ForMapWithDefaultFunction(map, defaultFunction);
  }

  // indebted to Guava's Functions.forMap()
  private static class ForMapWithDefaultFunction implements Function {

    private final Map map;
    private final Function defaultFunction;

    private ForMapWithDefaultFunction(
        final Map map, final Function defaultFunction) {
      this.map = checkNotNull(map);
      this.defaultFunction = checkNotNull(defaultFunction);
    }

    @Override
    public V apply(final K input) {
      final V result = map.get(input);
      return (result != null || map.containsKey(result)) ? result : defaultFunction.apply(input);
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(map, defaultFunction);
    }

    @Override
    public boolean equals(final Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null || getClass() != obj.getClass()) {
        return false;
      }
      final ForMapWithDefaultFunction other = (ForMapWithDefaultFunction) obj;
      return Objects.equal(this.map, other.map)
          && Objects.equal(this.defaultFunction, other.defaultFunction);
    }

    @Override
    public String toString() {
      return "asFunction(" + map + ", default=" + defaultFunction + ")";
    }
  }

  /**
   * Creates a {@link ImmutableMap} by pairing up a sequence of keys and values. The {@code n}-th
   * key is paired to the {@code n}-th value.  Neither null keys nor null values are permitted.  The
   * keys must be unique or an {@link IllegalArgumentException} will be thrown.  If the numberof
   * elements returned by the two {@link Iterable}s differ, an {@link IllegalArgumentException} will
   * be thrown.
   */
  public static  ImmutableMap copyParallelListsToMap(Iterable keys,
      Iterable values) {
    final ImmutableMap.Builder ret = ImmutableMap.builder();

    final Iterator keyIt = keys.iterator();
    final Iterator valueIt = values.iterator();

    while (keyIt.hasNext() && valueIt.hasNext()) {
      ret.put(keyIt.next(), valueIt.next());
    }

    if (!keyIt.hasNext() && !valueIt.hasNext()) {
      return ret.build();
    } else {
      if (keyIt.hasNext()) {
        throw new IllegalArgumentException(
            "When pairing keys and values, there were more keys than values");
      } else {
        throw new IllegalArgumentException(
            "When pairing keys and values, there were more values than keys");
      }
    }
  }

  /**
   * Creates a {@code LaxImmutableMapBuilder} which will behave exactly like a
   * {@link com.google.common.collect.ImmutableMap.Builder} except it will allow adding the
   * same key-value pair more than once. "Same" is determined by the value's equality method.
   * The behavior of this builder and the resulting {@link ImmutableMap} is deterministic.
   */
  public static  LaxImmutableMapBuilder immutableMapBuilderAllowingSameEntryTwice() {
    return new MonotonicLaxImmutableMapBuilder<>(false);
  }

  /**
   * Creates a {@code LaxImmutableMapBuilder} which will behave exactly like a
   * {@link com.google.common.collect.ImmutableMap.Builder} except it will silently ignore
   * any attempt to add a new entry with a previously seen key.
   * The behavior of this builder and the resulting {@link ImmutableMap} is deterministic.
   */
  public static  LaxImmutableMapBuilder immutableMapBuilderIgnoringDuplicates() {
    return new MonotonicLaxImmutableMapBuilder<>(true);
  }

  /**
   * Creates a {@code LaxImmutableMapBuilder} which will behave exactly like a
   * {@link com.google.common.collect.ImmutableMap.Builder} except that when attempting to add
   * a value for a previously seen key, the greater of the older value and the new value according
   * to the provided comparator will be used.  In case of a tie, the incumbent stays.
   *
   * The behavior of this builder and the resulting {@link ImmutableMap} will be deterministic if
   * the supplied comparator is.
   */
  public static  LaxImmutableMapBuilder immutableMapBuilderResolvingDuplicatesBy(Comparator conflictComparator) {
    return new NonMonotonicLaxImmutableMapBuilder<>(Ordering.from(conflictComparator));
  }

  /**
   * Returns a Guava {@link Function} to transform map entries to strings by taking the {@link
   * Object#toString()} of the key and the value and joining them with the supplied {@code
   * separator}. None of the key, value, or separator may be null.
   */
  public static  Function, String> toStringWithKeyValueSeparator(
      final String separator) {
    return new EntryJoinerFunction<>(separator);
  }

  private static class EntryJoinerFunction implements Function, String> {

    private final String separator;

    public EntryJoinerFunction(final String separator) {
      this.separator = checkNotNull(separator);
    }

    @Override
    public String apply(final Entry input) {
      return input.getKey() + separator + input.getValue();
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(separator);
    }

    @Override
    public boolean equals(final Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null || getClass() != obj.getClass()) {
        return false;
      }
      final EntryJoinerFunction other = (EntryJoinerFunction) obj;
      return Objects.equal(this.separator, other.separator);
    }
  }

  /**
   * Just like {@link Maps#transformEntries(Map, Maps.EntryTransformer)} but on an {@link Iterable}
   * of {@link Map.Entry}.
   */
  public static  Iterable> transformValues(
      Iterable> input,
      final Function valueTransformer) {
    return Iterables.transform(input, new Function, Entry>() {
      @Override
      public Entry apply(final Entry input) {
        return Maps.immutableEntry(input.getKey(), valueTransformer.apply(input.getValue()));
      }
    });
  }

  /**
   * Just like {@link Maps#transformEntries(Map, Maps.EntryTransformer)} but on an {@link Iterable}
   * of {@link Map.Entry}.
   */
  public static  Iterable> transformEntries(
      Iterable> input,
      final Maps.EntryTransformer entryTransformer) {
    return Iterables.transform(input, new Function, Entry>() {
      @Override
      public Entry apply(final Entry input) {
        return Maps.immutableEntry(input.getKey(),
            entryTransformer.transformEntry(input.getKey(), input.getValue()));
      }
    });
  }

  /**
   * Creates a {@link com.google.common.collect.Maps.EntryTransformer} which applies the supplied
   * {@link Function} to map entry values.
   */
  public static  Maps.EntryTransformer valueTransformer(
      final Function valueFunction) {
    checkNotNull(valueFunction);
    return new Maps.EntryTransformer() {
      @Override
      public V2 transformEntry(final K key, final V1 value) {
        return valueFunction.apply(value);
      }
    };
  }

  /**
   * Returns a wrapper around an {@link com.google.common.collect.ImmutableMultimap.Builder} which
   * hides the differences between it and {@link com.google.common.collect.ImmutableMap.Builder} for
   * the purpse of population.
   */
  public static  KeyValueSink asMapSink(ImmutableMultimap.Builder multimapB) {
    return new ImmutableMultimapBuilderSink<>(multimapB);
  }

  /**
   * Returns a wrapper around an {@link com.google.common.collect.ImmutableMap.Builder} which
   * hides the differences between it and {@link com.google.common.collect.ImmutableMultimap.Builder} for
   * the purpse of population. Beware {@link com.google.common.collect.ImmutableMap.Builder} will
   * still throw an exception for duplicate entries!
   */
  public static  KeyValueSink asMapSink(ImmutableMap.Builder mapB) {
    return new ImmutableMapBuilderSink<>(mapB);
  }
}

abstract class AbstractKeyValueSink implements KeyValueSink {

  @Override
  public final KeyValueSink put(final Entry entry) {
    put(entry.getKey(), entry.getValue());
    return this;
  }

  @Override
  public final KeyValueSink putAll(
      final Iterable> entries) {
    for (final Entry entry : entries) {
      put(entry.getKey(), entry.getValue());
    }
    return this;
  }
}

final class ImmutableMapBuilderSink extends AbstractKeyValueSink {
  final ImmutableMap.Builder builder;

  ImmutableMapBuilderSink(final ImmutableMap.Builder builder) {
    this.builder = checkNotNull(builder);
  }

  @Override
  public KeyValueSink put(final K key, final V value) {
    builder.put(key, value);
    return this;
  }


}

final class ImmutableMultimapBuilderSink extends AbstractKeyValueSink {
  final ImmutableMultimap.Builder builder;

  ImmutableMultimapBuilderSink(final ImmutableMultimap.Builder builder) {
    this.builder = checkNotNull(builder);
  }

  @Override
  public KeyValueSink put(final K key, final V value) {
    builder.put(key, value);
    return this;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy