package com.miguelfonseca.completely;
import com.miguelfonseca.completely.data.ScoredObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import static com.miguelfonseca.completely.common.Precondition.checkPointer;
/**
* Aggregator to collect, merge and transform {@link ScoredObject} elements.
*/
public class Aggregator<T>
{
private final Map<T, Double> scores;
private final Comparator<ScoredObject<T>> comparator;
/**
* Constructs a new {@link Aggregator}.
*/
public Aggregator()
{
this(null);
}
/**
* Constructs a new {@link Aggregator}.
*/
public Aggregator(@Nullable Comparator<ScoredObject<T>> comparator)
{
this.scores = new HashMap<>();
this.comparator = comparator;
}
/**
* Adds a single element, if not present already.
*
* @throws NullPointerException if {@code element} is null;
*/
public boolean add(ScoredObject<T> element)
{
return addAll(Arrays.asList(element));
}
/**
* Adds a collection of elements, if not present already.
*
* @throws NullPointerException if {@code elements} is null or contains a null element;
*/
public boolean addAll(Collection<ScoredObject<T>> elements)
{
checkPointer(elements != null);
boolean result = false;
for (ScoredObject<T> element : elements)
{
checkPointer(element != null);
Double score = scores.get(element.getObject());
if (score == null)
{
scores.put(element.getObject(), element.getScore());
result = true;
}
else if (element.getScore() != 0)
{
scores.put(element.getObject(), score + element.getScore());
result = true;
}
}
return result;
}
/**
* Returns {@code true} if no elements exist.
*/
public boolean isEmpty()
{
return scores.isEmpty();
}
/**
* Retain the elements in common, compared according to the objects scored.
*
* @throws NullPointerException if {@code element} is null;
*/
public boolean retain(ScoredObject<T> element)
{
return retainAll(Arrays.asList(element));
}
/**
* Retains the elements in common, compared according to the objects scored.
*
* @throws NullPointerException if {@code elements} is null or contains a null element;
*/
public boolean retainAll(Collection<ScoredObject<T>> elements)
{
checkPointer(elements != null);
// Intersect
Collection<T> set = new HashSet<>();
for (ScoredObject<T> element : elements)
{
checkPointer(element != null);
set.add(element.getObject());
}
boolean result = scores.keySet().retainAll(set);
// Combine scores
for (ScoredObject<T> element : elements)
{
if (element.getScore() == 0)
{
continue;
}
Double score = scores.get(element.getObject());
if (score != null)
{
scores.put(element.getObject(), score + element.getScore());
result = true;
}
}
return result;
}
/**
* Returns the number of elements.
*/
public int size()
{
return scores.size();
}
/**
* Returns a {@link List} of all objects scored, sorted according to the default comparator.
*/
public List<T> values()
{
List<ScoredObject<T>> list = new ArrayList<>();
for (Entry<T, Double> entry : scores.entrySet())
{
list.add(new ScoredObject<>(entry.getKey(), entry.getValue()));
}
Collections.sort(list, comparator);
List<T> result = new ArrayList<>();
for (ScoredObject<T> element : list)
{
result.add(element.getObject());
}
return result;
}
}