MapBuilder.java
package com.brianmearns.crafter;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* A {@link BuilderInterface} of {@linkplain Map map} objects.
*
* <p>
* Map elements are {@linkplain #put(Object, Object) put} into the builder as a key and its value. Putting the same
* key again will effectively replace previous value associated with that key.
* </p>
*
* @author Brian Mearns <bmearns@ieee.org>
*/
@SuppressWarnings("unused")
public abstract class MapBuilder<K, V> implements BuilderInterface<Map<K,V>> {
/**
* Create a new builder instance which will build {@link Map Map<K,V>} instances.
*
* @param <K> The type for the keys that will be put into the built maps.
* @param <V> The type for the value that will be put into the built maps.
*
* @return The newly constructed {@link MapBuilder}.
*/
@Nonnull
public static <K,V> MapBuilder<K,V> create() {
return new DefaultMapBuilder<>();
}
/**
* Create a new builder instance which will build {@link Map Map<K,V>} instances.
*
* <p>
* This is an alternative convenience interface over {@link #create()} with no args. Instead of specifying
* type parameters on the method call, you can just pass in the classes you want to use and the compiler will
* determine the correct type parameters automatically.
* </p>
*
* @param keyCls The class of keys that will be put into the build maps.
* @param valueCls The class of values that will be put into the build maps.
*
* @param <K> The type for the keys that will be put into the built maps.
* @param <V> The type for the value that will be put into the built maps.
*
* @return The newly constructed {@link MapBuilder}.
*/
@Nonnull
public static <K,V> MapBuilder<K,V> create(@Nonnull Class<K> keyCls, @Nonnull Class<V> valueCls) {
return new DefaultMapBuilder<>();
}
/**
* This is the implementation specific method for putting a value in the map.
*
* @param key The key to add (or replace) in the map.
* @param valueSupplier A {@link Supplier} for the value to put in the map.
*/
protected abstract void putSupplier(@Nullable K key, @Nonnull Supplier<? extends V> valueSupplier);
/**
* Put the given value in the builder's map.
*
* @param key The key at which to put the {@code value}.
* @param value The value to put at the given {@code key}.
*
* @return This builder instance itself, for chaining convenience.
*/
public MapBuilder<K,V> put(@Nullable K key, @Nullable V value) {
putSupplier(key, Suppliers.ofInstance(value));
return this;
}
/**
* Put a value in the builder's map, represented by a BuilderInterface of that value.
*
* <p>
* Note that the given builder is not {@linkplain BuilderInterface#get() invoked} to build the value
* right away, it is invoked once for each call to {@link #get()}.
*
* @param key The key at which to put the {@code value}.
* @param value The value to put at the given {@code key}.
*
* @return This builder instance itself, for chaining convenience.
*/
public MapBuilder<K,V> put(@Nullable K key, @Nonnull BuilderInterface<? extends V> value) {
putSupplier(key, value);
return this;
}
/**
* Place the given key-value pair in the builder, if and only if the
* given boolean is {@code true}. Otherwise has no effect on the state of the builder.
*
* @param key The key at which to put the {@code value}.
* @param value The value to put at the given {@code key}.
* @param put Whether or not to actually update the builder.
*
* @return This builder instance itself, for chaining convenience.
*/
public MapBuilder<K,V> maybePut(@Nullable K key, @Nullable V value, boolean put) {
if(put) {
putSupplier(key, Suppliers.ofInstance(value));
}
return this;
}
/**
* Place the given key-value pair in the builder, if and only if the
* given boolean is {@code true}. Otherwise has no effect on the state of the builder.
*
* @param key The key at which to put the {@code value}.
* @param value Builder of the value to put at the given {@code key}.
* @param put Whether or not to actually update the builder.
*
* @return This builder instance itself, for chaining convenience.
*/
public MapBuilder<K,V> maybePut(@Nullable K key, @Nonnull BuilderInterface<? extends V> value, boolean put) {
if(put) {
putSupplier(key, value);
}
return this;
}
/**
* Apply the given function to {@code this} object, and return {@code this} object again.
*
* <p>
* This is simply a way to add some arbitrary code in the middle of a chain of method invocations.
* You could use the {@link Function} to perform some complex logic to configure the builder, for instance.
*
* <p>
* The return value of the function is ignored, but for safety and clarity, it should be a void return.
*
* @param function The {@link Function} to be invoked on {@code this} object.
*
* @return This {@code MapBuilder} itself, for chaining convenience.
*/
public abstract MapBuilder<K,V> apply(Function<MapBuilder<K,V>, Void> function);
@Override
@Nonnull
public abstract Map<K, V> get() throws IncompleteBuilderException;
@Nonnull
public abstract MapBuilder<K, V> maybe(boolean yes);
@Nonnull
public abstract MapBuilder<K, V> endMaybe();
@Nonnull
public abstract MapBuilder<K, V> always();
protected static class DefaultMapBuilder<K, V> extends MapBuilder<K, V> {
@Nonnull
private List<Entry<K,V>> entries;
public DefaultMapBuilder() {
entries = new LinkedList<>();
}
@Override
@Nonnull
public Map<K, V> get() throws IncompleteBuilderException {
return buildMap(entries);
}
@Nonnull
@Override
public MapBuilder<K, V> maybe(boolean yes) {
if(yes) {
return this;
}
return new NeverMapBuilder<>(this, this);
}
@Override
@Nonnull
public MapBuilder<K, V> endMaybe() {
return this;
}
@Override
@Nonnull
public MapBuilder<K, V> always() {
return this;
}
/**
* Helper function called from {@link #get()} to actually build and populate
* the map with the given entries. This makes it easier to override if you want to
* create a different type of map.
*
* This will typically delegate to {@link #createMap(int)}.
*/
@Nonnull
protected Map<K, V> buildMap(@Nonnull List<Entry<K,V>> entries) {
Map<K,V> map = createMap(entries.size());
for(Entry<K,V> entry : entries) {
map.put(entry.getKey(), entry.getValue());
}
return map;
}
/**
* Helper methods called by {@link #buildMap(List)} to create the initial map. This is the easiest
* way to override the implementation of the Map interface you want to use.
*
* @param size The approximate number of entries that the map will need to hold initially.
*/
@Nonnull
protected Map<K, V> createMap(int size) {
return new HashMap<>(size);
}
@Override
protected void putSupplier(@Nullable K key, @Nonnull Supplier<? extends V> valueSupplier) {
entries.add(new Entry<>(key, valueSupplier));
}
@Override
public MapBuilder<K,V> apply(Function<MapBuilder<K,V>, Void> function) {
function.apply(this);
return this;
}
private static class Entry<K,V> {
@Nullable
private final K key;
@Nonnull
private final Supplier<? extends V> value;
protected Entry(@Nullable K key, @Nonnull Supplier<? extends V> value) {
this.key = key;
this.value = value;
}
@Nullable
public K getKey() {
return key;
}
@Nullable
public V getValue() {
return value.get();
}
}
}
protected static class NeverMapBuilder<K, V> extends MapBuilder<K, V> {
@Nonnull
private final MapBuilder<K, V> alwaysBuilder;
@Nonnull
private final MapBuilder<K, V> parent;
protected NeverMapBuilder(@Nonnull MapBuilder<K, V> alwaysBuilder, @Nonnull MapBuilder<K, V> parent) {
this.alwaysBuilder = alwaysBuilder;
this.parent = parent;
}
@Override
protected void putSupplier(@Nullable K key, @Nonnull Supplier<? extends V> valueSupplier) {
//Do nothing;
}
@Override
public MapBuilder<K, V> apply(Function<MapBuilder<K, V>, Void> function) {
return this;
}
@Nonnull
@Override
public Map<K, V> get() throws IncompleteBuilderException {
return alwaysBuilder.get();
}
@Nonnull
@Override
public MapBuilder<K, V> maybe(boolean yes) {
return new NeverMapBuilder<>(alwaysBuilder, this);
}
@Nonnull
@Override
public MapBuilder<K, V> endMaybe() {
return parent;
}
@Nonnull
@Override
public MapBuilder<K, V> always() {
return alwaysBuilder;
}
}
}