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

com.github.tonivade.purefun.data.ImmutableTreeMap Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2018-2024, Antonio Gabriel Muñoz Conejo 
 * Distributed under the terms of the MIT License
 */
package com.github.tonivade.purefun.data;

import static com.github.tonivade.purefun.core.Precondition.checkNonNull;
import static java.util.stream.Collectors.collectingAndThen;
import com.github.tonivade.purefun.core.Equal;
import com.github.tonivade.purefun.core.Function1;
import com.github.tonivade.purefun.core.Matcher1;
import com.github.tonivade.purefun.core.Operator2;
import com.github.tonivade.purefun.core.Producer;
import com.github.tonivade.purefun.core.Tuple;
import com.github.tonivade.purefun.core.Tuple2;
import com.github.tonivade.purefun.type.Option;
import com.github.tonivade.purefun.type.Try;
import java.io.Serial;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.SequencedMap;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.pcollections.PSortedMap;
import org.pcollections.TreePMap;

/**
 * Similar to a TreeMap
 * @param  the type of keys maintained by this map
 * @param  the type of mapped values
 */
public interface ImmutableTreeMap extends ImmutableMap {

  Comparator comparator();

  NavigableMap toNavigableMap();

  default SequencedMap toSequencedMap() {
    return toNavigableMap();
  }

  default SortedMap toSortedMap() {
    return toNavigableMap();
  }

  @Override
  ImmutableTreeMap put(K key, V value);

  @Override
  ImmutableTreeMap putAll(ImmutableMap other);

  @Override
  ImmutableTreeMap remove(K key);

  @Override
  ImmutableTreeMap merge(K key, V value, Operator2 merger);

  ImmutableTreeMap headMap(K toKey);
  ImmutableTreeMap tailMap(K fromKey);
  Option> headEntry();
  Option> tailEntry();
  Option> higherEntry(K key);
  Option> lowerEntry(K key);
  Option> floorEntry(K key);
  Option> ceilingEntry(K key);

  default Option headKey() {
    return headEntry().map(Tuple2::get1);
  }

  default Option tailKey() {
    return tailEntry().map(Tuple2::get1);
  }

  default Option higherKey(K key) {
    return higherEntry(key).map(Tuple2::get1);
  }

  default Option lowerKey(K key) {
    return lowerEntry(key).map(Tuple2::get1);
  }

  default Option floorKey(K key) {
    return floorEntry(key).map(Tuple2::get1);
  }

  default Option ceilingKey(K key) {
    return floorEntry(key).map(Tuple2::get1);
  }

  @Override
  default  ImmutableTreeMap bimap(Function1 keyMapper, Function1 valueMapper) {
    return ImmutableTreeMap.from(entries().map(tuple -> tuple.map(keyMapper, valueMapper)));
  }

  default  ImmutableTreeMap bimap(Comparator comparator, Function1 keyMapper, Function1 valueMapper) {
    return ImmutableTreeMap.from(comparator, entries().map(tuple -> tuple.map(keyMapper, valueMapper)));
  }

  @Override
  default  ImmutableTreeMap mapKeys(Function1 mapper) {
    return ImmutableTreeMap.from(entries().map(tuple -> tuple.map1(mapper)));
  }

  default  ImmutableTreeMap mapKeys(Comparator comparator, Function1 mapper) {
    return ImmutableTreeMap.from(comparator, entries().map(tuple -> tuple.map1(mapper)));
  }

  @Override
  default  ImmutableTreeMap mapValues(Function1 mapper) {
    return ImmutableTreeMap.from(entries().map(tuple -> tuple.map2(mapper)));
  }

  @Override
  default ImmutableTreeMap filterKeys(Matcher1 filter) {
    return ImmutableTreeMap.from(entries().filter(tuple -> filter.match(tuple.get1())));
  }

  @Override
  default ImmutableTreeMap filterValues(Matcher1 filter) {
    return ImmutableTreeMap.from(entries().filter(tuple -> filter.match(tuple.get2())));
  }

  @Override
  default ImmutableTreeMap putIfAbsent(K key, V value) {
    if (containsKey(key)) {
      return this;
    }
    return put(key, value);
  }

  @Override
  default V getOrDefault(K key, Producer supplier) {
    return get(key).getOrElse(supplier);
  }

  @Override
  default boolean isEmpty() {
    return size() == 0;
  }

  @SafeVarargs
  static  ImmutableTreeMap of(Tuple2... entries) {
    return from(naturalOrder(), ImmutableSet.of(entries));
  }

  static  Tuple2 entry(K key, V value) {
    return Tuple2.of(key, value);
  }

  static  ImmutableTreeMap from(Comparator comparator, Map map) {
    return new PImmutableTreeMap<>(comparator, map);
  }

  static  ImmutableTreeMap from(Map map) {
    return new PImmutableTreeMap<>(naturalOrder(), map);
  }

  @SuppressWarnings("unchecked")
  static  ImmutableTreeMap empty() {
    return (ImmutableTreeMap) PImmutableTreeMap.EMPTY;
  }

  static  ImmutableTreeMap from(Stream> entries) {
    return from(naturalOrder(), entries);
  }

  static  ImmutableTreeMap from(Comparator comparator, Stream> entries) {
    return from(comparator, ImmutableSet.from(entries));
  }

  static  ImmutableTreeMap from(ImmutableSet> entries) {
    return from(naturalOrder(), entries);
  }

  static  ImmutableTreeMap from(Comparator comparator, ImmutableSet> entries) {
    TreeMap treeMap = entries.stream()
        .collect(toTreeMap(comparator, Tuple2::get1, Tuple2::get2));
    return new PImmutableTreeMap<>(treeMap);
  }

  static  ImmutableTreeMap from(Set> entries) {
    return from(naturalOrder(), entries);
  }

  static  ImmutableTreeMap from(Comparator comparator, Set> entries) {
    TreeMap treeMap = entries.stream()
        .collect(toTreeMap(comparator, Map.Entry::getKey, Map.Entry::getValue));
    return new PImmutableTreeMap<>(treeMap);
  }

  static  Collector> toImmutableTreeMap(
      Function1 keyMapper, Function1 valueMapper) {
    return toImmutableTreeMap(naturalOrder(), keyMapper, valueMapper);
  }

  static  Collector> toImmutableTreeMap(
      Comparator comparator, Function1 keyMapper, Function1 valueMapper) {
    Collector> toTreeMap = toTreeMap(comparator, keyMapper, valueMapper);
    return collectingAndThen(toTreeMap, PImmutableTreeMap::new);
  }

  static , V> Builder builder() {
    return new Builder<>();
  }

  final class Builder, V> {

    private final NavigableMap map = new TreeMap<>();

    private Builder() { }

    public Builder put(K key, V value) {
      map.put(key, value);
      return this;
    }

    public ImmutableTreeMap build() {
      return ImmutableTreeMap.from(map);
    }
  }

  @SuppressWarnings("unchecked")
  private static  Comparator naturalOrder() {
    return (Comparator) Comparator.naturalOrder();
  }

  final class PImmutableTreeMap implements ImmutableTreeMap, Serializable {

    @Serial
    private static final long serialVersionUID = -3269335569221894587L;

    private static final ImmutableTreeMap EMPTY = new PImmutableTreeMap<>(TreePMap.empty(naturalOrder()));

    private static final Equal> EQUAL =
        Equal.>of().comparing(a -> a.backend);

    private final PSortedMap backend;

    private PImmutableTreeMap(Comparator comparator, Map backend) {
      this(TreePMap.from(comparator, backend));
    }

    private PImmutableTreeMap(SortedMap backend) {
      this(TreePMap.fromSortedMap(backend));
    }

    private PImmutableTreeMap(PSortedMap backend) {
      this.backend = checkNonNull(backend);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Comparator comparator() {
      return (Comparator) backend.comparator();
    }

    @Override
    public Map toMap() {
      return toNavigableMap();
    }

    @Override
    public NavigableMap toNavigableMap() {
      return new TreeMap<>(backend);
    }

    @Override
    public ImmutableTreeMap put(K key, V value) {
      return new PImmutableTreeMap<>(backend.plus(key, value));
    }

    @Override
    public ImmutableTreeMap putAll(ImmutableMap other) {
      return new PImmutableTreeMap<>(backend.plusAll(other.toMap()));
    }

    @Override
    public ImmutableTreeMap remove(K key) {
      return new PImmutableTreeMap<>(backend.minus(key));
    }

    @Override
    public Option get(K key) {
      return Option.of(backend.get(key));
    }

    @Override
    public ImmutableTreeMap merge(K key, V value, Operator2 merger) {
      var oldValue = backend.get(key);
      var newValue = oldValue == null ? value : merger.apply(oldValue, value);
      if (newValue == null) {
        return new PImmutableTreeMap<>(backend.minus(key));
      }
      return new PImmutableTreeMap<>(backend.plus(key, newValue));
    }

    @Override
    public ImmutableTreeMap headMap(K toKey) {
      return new PImmutableTreeMap<>(backend.headMap(toKey, false));
    }

    @Override
    public ImmutableTreeMap tailMap(K fromKey) {
      return new PImmutableTreeMap<>(backend.tailMap(fromKey, false));
    }

    @Override
    public Option> headEntry() {
      return Try.of(() -> Tuple.from(backend.firstEntry())).toOption();
    }

    @Override
    public Option> tailEntry() {
      return Try.of(() -> Tuple.from(backend.lastEntry())).toOption();
    }

    @Override
    public Option> higherEntry(K key) {
      return Try.of(() -> Tuple.from(backend.higherEntry(key))).toOption();
    }

    @Override
    public Option> lowerEntry(K key) {
      return Try.of(() -> Tuple.from(backend.lowerEntry(key))).toOption();
    }

    @Override
    public Option> floorEntry(K key) {
      return Try.of(() -> Tuple.from(backend.floorEntry(key))).toOption();
    }

    @Override
    public Option> ceilingEntry(K key) {
      return Try.of(() -> Tuple.from(backend.ceilingEntry(key))).toOption();
    }

    @Override
    public Sequence values() {
      return ImmutableList.from(backend.values());
    }

    @Override
    public ImmutableSet keys() {
      return ImmutableSet.from(backend.keySet());
    }

    @Override
    public ImmutableSet> entries() {
      return ImmutableSet.from(backend.entrySet()).map(Tuple::from);
    }

    @Override
    public int size() {
      return backend.size();
    }

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

    @Override
    public boolean equals(Object obj) {
      return EQUAL.applyTo(this, obj);
    }

    @Override
    public String toString() {
      return "ImmutableTreeMap(" + backend + ")";
    }

    @Serial
    private Object readResolve() {
      // XXX: this is a issue in pcollections: see https://github.com/hrldcpr/pcollections/pull/115
      if (backend.size() == 0) {
        return EMPTY;
      }
      return this;
    }
  }

  private static  Collector> toTreeMap(
      Comparator comparator,
      Function1 keyMapper,
      Function1 valueMapper) {
    return Collectors.toMap(keyMapper::apply, valueMapper::apply, ImmutableTreeMap::throwingMerge, () -> new TreeMap<>(comparator));
  }

  private static  V throwingMerge(V a, V b) {
    throw new IllegalArgumentException("conflict detected");
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy