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

org.fissore.steroids.SteroidMap Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
package org.fissore.steroids;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 * SteroidMap is a {@link Map} on steroids.
 * It decorates {@link Map} providing a fluent interface and a shorter syntax. It allows you to navigate a map structure without using casts.
 * It doesn't require any code to work with other frameworks/libraries: if they know how to deal with {@link Map}s, they work with SteroidMap as well.
 * Custom implementations should implement SteroidMap and delegate the actual storage to a backing map implementation, such as {@link HashMap} or {@link java.util.concurrent.ConcurrentHashMap}.
 *
 * @param  the type of the keys
 */
public interface SteroidMap extends Map {

  /* content manipulation */

  /**
   * {@link #put(Object, Object) Puts} a value if not null. Fluent version of {@link #put(Object, Object)}
   *
   * @param key   key with which the specified value is to be associated
   * @param value value to be associated with the specified key
   * @return this instance
   */
  default SteroidMap add(K key, Object value) {
    if (value != null) {
      put(key, value);
    }
    return this;
  }

  /**
   * {@link #put(Object, Object) Puts} all non null values from given maps into this map. Fluent version of {@link #putAll(Map)}
   *
   * @param sources mappings to be stored in this map
   * @return this instance
   * @see #addAll(Stream)
   */
  default SteroidMap addAll(Map... sources) {
    return addAll(Stream.of(sources));
  }

  /**
   * {@link #put(Object, Object) Puts} all non null values from given maps into this map. Fluent version of {@link #putAll(Map)}
   *
   * @param sources mappings to be stored in this map
   * @return this instance
   * @see #addAll(Stream)
   */
  default SteroidMap addAll(Collection> sources) {
    return addAll(sources.stream());
  }

  /**
   * {@link #put(Object, Object) Puts} all non null values from given maps into this map. Fluent version of {@link #putAll(Map)}
   *
   * @param sources mappings to be stored in this map
   * @return this instance
   */
  default SteroidMap addAll(Stream> sources) {
    synchronized (this) {
      sources.forEach(source -> source.forEach(this::add));
    }
    return this;
  }

  /**
   * {@link #put(Object, Object) Puts} non null keys from source into this map. If keys is not specified, it behaves as {@link #addAll(Map[])}
   *
   * @param source the map to add to this map
   * @param keys   if specified, only given keys will be added
   * @return this instance
   */
  default SteroidMap addFrom(Map source, K... keys) {
    return addFrom(source, new HashSet<>(Arrays.asList(keys)));
  }

  /**
   * {@link #put(Object, Object) Puts} non null keys from source into this map. If keys is not specified, it behaves as {@link #addAll(Map[])}
   *
   * @param source the map to add to this map
   * @param keys   if specified, only these keys will be added
   * @return this instance
   */
  default SteroidMap addFrom(Map source, Collection keys) {
    if (source == null) {
      return this;
    }

    if (keys.size() == 0) {
      return addAll(source);
    }

    synchronized (this) {
      source.entrySet().stream()
          .filter(e -> keys.contains(e.getKey()))
          .forEach(e -> add(e.getKey(), e.getValue()));
    }

    return this;
  }

  /**
   * Renames the specified key, if present. It runs a in a synchronized block
   *
   * @param oldKey the old key
   * @param newKey the new key
   * @return this instance
   */
  default SteroidMap renameKey(K oldKey, K newKey) {
    synchronized (this) {
      if (!containsKey(oldKey)) {
        return this;
      }
      put(newKey, get(oldKey));
      remove(oldKey);
    }
    return this;
  }

  /**
   * Removes given keys from this map. Fluent version of {@link #remove(Object)}
   *
   * @param keys the keys to remove
   * @return this instance
   * @see #del(Stream)
   */
  default SteroidMap del(K... keys) {
    return del(Stream.of(keys));
  }

  /**
   * Removes given keys from this map. Fluent version of {@link #remove(Object)}
   *
   * @param keys the keys to remove
   * @return this instance
   * @see #del(Stream)
   */
  default SteroidMap del(Collection keys) {
    return del(keys.stream());
  }

  /**
   * Removes given keys from this map. Fluent version of {@link #remove(Object)}
   *
   * @param keys the keys to remove
   * @return this instance
   */
  default SteroidMap del(Stream keys) {
    synchronized (this) {
      keys.forEach(this::remove);
    }
    return this;
  }

  /* content extraction */

  /**
   * If given key is {@link #valued(Object) valued}, it's applied to provided valueReturner. Otherwise, defaultValue is returned.
   * Both checking if the key is {@link #valued(Object) valued} and applying valueReturner run in a synchronized block
   *
   * @param key           the key
   * @param defaultValue  the value to return if key is not {@link #valued(Object) valued}
   * @param valueReturner the function to apply the key to
   * @param            the return type
   * @return either value from map or defaultValue
   */
  default  V defaultIfMissing(K key, V defaultValue, Function valueReturner) {
    synchronized (this) {
      if (valued(key)) {
        return valueReturner.apply(key);
      }
    }
    return defaultValue;
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Long
   *
   * @param key the key
   * @return value associated to key casted to Long
   */
  default long l(K key) {
    return (Long) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Long. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @return value associated to key, casted to Long. defaultValue if key is not {@link #valued(Object) valued}
   */
  default long l(K key, Long defaultValue) {
    return defaultIfMissing(key, defaultValue, this::l);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Integer
   *
   * @param key the key
   * @return value associated to key casted to Integer
   */
  default int i(K key) {
    return (Integer) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Integer. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @return value associated to key, casted to Integer. defaultValue if key is not {@link #valued(Object) valued}
   */
  default int i(K key, Integer defaultValue) {
    return defaultIfMissing(key, defaultValue, this::i);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Double
   *
   * @param key the key
   * @return value associated to key casted to Double
   */
  default double d(K key) {
    return (Double) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Double. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @return value associated to key, casted to Double. defaultValue if key is not {@link #valued(Object) valued}
   */
  default double d(K key, Double defaultValue) {
    return defaultIfMissing(key, defaultValue, this::d);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Float
   *
   * @param key the key
   * @return value associated to key casted to Float
   */
  default float f(K key) {
    return (Float) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Float. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @return value associated to key, casted to Float. defaultValue if key is not {@link #valued(Object) valued}
   */
  default float f(K key, Float defaultValue) {
    return defaultIfMissing(key, defaultValue, this::f);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a String
   *
   * @param key the key
   * @return value associated to key casted to String
   */
  default String s(K key) {
    return (String) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a String. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @return value associated to key, casted to String. defaultValue if key is not {@link #valued(Object) valued}
   */
  default String s(K key, String defaultValue) {
    return defaultIfMissing(key, defaultValue, this::s);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Boolean
   *
   * @param key the key
   * @return value associated to key casted to Boolean
   */
  default boolean b(K key) {
    return (Boolean) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Boolean. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @return value associated to key, casted to Boolean. defaultValue if key is not {@link #valued(Object) valued}
   */
  default boolean b(K key, Boolean defaultValue) {
    return defaultIfMissing(key, defaultValue, this::b);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to type V
   *
   * @param key the key
   * @param  the return type
   * @return value associated to key casted to type V
   */
  default  V o(K key) {
    return (V) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and transform it using given function
   *
   * @param key         the key
   * @param transformer a function used to change original value
   * @param          the return type
   * @return value associated to key and transformed using function
   */
  default  V o(K key, Function transformer) {
    return transformer.apply(get(key));
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to type of given Class
   *
   * @param key   the key
   * @param clazz the clazz, useful only to make the compiler happy about type V
   * @param    the return type
   * @return value associated to key casted to type V
   */
  @SuppressWarnings("unchecked")
  default  V o(K key, Class clazz) {
    return (V) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Object. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @param           the return type
   * @return value associated to key, casted to Object. defaultValue if key is not {@link #valued(Object) valued}
   */
  @SuppressWarnings("unchecked")
  default  V o(K key, V defaultValue) {
    return defaultIfMissing(key, defaultValue, this::o);
  }

  /**
   * {@link #get(Object) Gets} given key and transform it using given function. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param transformer  a function used to change original value
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @param           the return type
   * @return value associated to key, tranformed using function. defaultValue if key is not {@link #valued(Object) valued}
   */
  default  V o(K key, Function transformer, V defaultValue) {
    return defaultIfMissing(key, defaultValue, (k) -> o(k, transformer));
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Date
   *
   * @param key the key
   * @return value associated to key casted to Date
   */
  default Date date(K key) {
    return (Date) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Date. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @return value associated to key, casted to Date. defaultValue if key is not {@link #valued(Object) valued}
   */
  default Date date(K key, Date defaultValue) {
    return defaultIfMissing(key, defaultValue, this::date);
  }

  /**
   * A key is valued if {@link #get(Object) get(key)} != null
   *
   * @param key the key
   * @return true if {@link #get(Object) get(key)} != null
   */
  default boolean valued(K key) {
    return get(key) != null;
  }

  /**
   * Opposite of {@link #valued(Object)}
   *
   * @param key the key
   * @return true if {@link #get(Object) get(key)} == null
   */
  default boolean notValued(K key) {
    return !valued(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Collection<V>
   *
   * @param key the key
   * @param  the return type
   * @return value associated to key casted to Collection<V>
   */
  @SuppressWarnings("unchecked")
  default  Collection collection(K key) {
    return (Collection) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a Collection<V>. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @param           the return type
   * @return value associated to key, casted to Collection<V>. defaultValue if key is not {@link #valued(Object) valued}
   */
  default  Collection collection(K key, Collection defaultValue) {
    return defaultIfMissing(key, defaultValue, this::collection);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a List<V>
   *
   * @param key the key
   * @param  the return type
   * @return value associated to key casted to List<V>
   */
  @SuppressWarnings("unchecked")
  default  List list(K key) {
    return (List) get(key);
  }

  /**
   * {@link #get(Object) Gets} given key and cast it to a List<V>. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @param           the return type
   * @return value associated to key, casted to List<V>. defaultValue if key is not {@link #valued(Object) valued}
   */
  default  List list(K key, List defaultValue) {
    return defaultIfMissing(key, defaultValue, this::list);
  }

  /**
   * {@link #get(Object) Gets} given key, cast it to a Collection<V> and returns its Stream<V>
   *
   * @param key the key
   * @param  the return type
   * @return value associated to key casted to a Collection<V> and converted to a Stream<V>
   */
  @SuppressWarnings("unchecked")
  default  Stream stream(K key) {
    return (Stream) collection(key).stream();
  }

  /**
   * {@link #get(Object) Gets} given key, cast it to a Collection<V> and returns its Stream<V>. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @param           the return type
   * @return value associated to key casted to a Collection<V> and converted to a Stream<V>. defaultValue if key is not {@link #valued(Object) valued}
   */
  default  Stream stream(K key, Collection defaultValue) {
    return collection(key, defaultValue).stream();
  }

  /**
   * Creates a new SteroidMap made of given keys only. Keys must be {@link #valued(Object) valued}
   *
   * @param keys the keys used to create a new, filtered SteroidMap
   * @return a new, filtered, SteroidMap
   * @see #subMap(Stream)
   */
  default SteroidMap subMap(K... keys) {
    return subMap(Stream.of(keys));
  }

  /**
   * Creates a new SteroidMap made of given keys only. Keys must be {@link #valued(Object) valued}
   *
   * @param keys the keys used to create a new, filtered SteroidMap
   * @return a new, filtered, SteroidMap
   * @see #subMap(Stream)
   */
  default SteroidMap subMap(Collection keys) {
    return subMap(keys.stream());
  }

  /**
   * Creates a new SteroidMap made of given keys only. Keys must be {@link #valued(Object) valued}
   *
   * @param keys the keys used to create a new, filtered SteroidMap
   * @return a new, filtered, SteroidMap
   */
  SteroidMap subMap(Stream keys);

  /**
   * Creates a new SteroidMap, backed by given backingMap and made of given keys only. Keys must be {@link #valued(Object) valued}
   *
   * @param backingMap the actual map to use as storage
   * @param keys       the keys used to create a new, filtered SteroidMap
   * @return a new, filtered, SteroidMap
   * @see #subMap(Map, Stream)
   */
  default SteroidMap subMap(Map backingMap, K... keys) {
    return subMap(backingMap, Stream.of(keys));
  }

  /**
   * Creates a new SteroidMap, backed by given backingMap and made of given keys only. Keys must be {@link #valued(Object) valued}
   *
   * @param backingMap the actual map to use as storage
   * @param keys       the keys used to create a new, filtered SteroidMap
   * @return a new, filtered, SteroidMap
   * @see #subMap(Map, Stream)
   */
  default SteroidMap subMap(Map backingMap, Collection keys) {
    return subMap(backingMap, keys.stream());
  }

  /**
   * Creates a new SteroidMap, backed by given backingMap and made of given keys only. Keys must be {@link #valued(Object) valued}
   *
   * @param backingMap the actual map to use as storage
   * @param keys       the keys used to create a new, filtered SteroidMap
   * @return a new, filtered, SteroidMap
   */
  SteroidMap subMap(Map backingMap, Stream keys);

  /**
   * {@link #get(Object) Gets} given key and, if value is not of type SteroidMap, a new SteroidMap is created using value as backing map. Otherwise value is casted to SteroidMap and returned
   *
   * @param key the key
   * @return value associated to key, either casted to SteroidMap or used as backing map for a new SteroidMap
   */
  SteroidMap map(K key);

  /**
   * {@link #get(Object) Gets} given key and, if value is not of type SteroidMap, a new SteroidMap is created using value as backing map. Otherwise value is casted to SteroidMap and returned. If key is not {@link #valued(Object) valued}, it returns defaultValue
   *
   * @param key          the key
   * @param defaultValue the defaultValue to return if key is not {@link #valued(Object) valued}
   * @return value associated to key, either casted to SteroidMap or used as backing map for a new SteroidMap
   */
  default SteroidMap map(K key, SteroidMap defaultValue) {
    return defaultIfMissing(key, defaultValue, this::map);
  }

  /**
   * {@link #get(Object) Gets} given key, cast it to a Collection<? extends SteroidMap<K>> and returns its Stream<? extends SteroidMap<K>>. Each entry is evaluated: if it's not of type SteroidMap, a new SteroidMap is created using value as backing map
   *
   * @param key the key
   * @return value associated to key casted to a Collection<? extends SteroidMap<K>> and converted to a Stream<? extends SteroidMap<K>>
   */
  default Stream> maps(K key) {
    return stream(key).map(this::ensureMapIsOnSteroid);
  }

  SteroidMap ensureMapIsOnSteroid(Object value);

  /**
   * Returns a shallow copy of this map
   *
   * @return a shallow copy of this map
   */
  SteroidMap copy();

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy