/*****************************************************************************
* Limpet - the Lightweight InforMation ProcEssing Toolkit
* http://limpet.info
*
* (C) 2015-2016, Deep Blue C Technologies Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html)
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*****************************************************************************/
package info.limpet.data.operations.arithmetic;
import info.limpet.ICollection;
import info.limpet.ICommand;
import info.limpet.IContext;
import info.limpet.IQuantityCollection;
import info.limpet.IStore;
import info.limpet.IStoreItem;
import info.limpet.ITemporalQuantityCollection;
import info.limpet.data.commands.AbstractCommand;
import info.limpet.data.impl.QuantityCollection;
import info.limpet.data.impl.TemporalQuantityCollection;
import info.limpet.data.operations.CollectionComplianceTests;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.measure.Measure;
import javax.measure.quantity.Quantity;
import javax.measure.unit.Unit;
public abstract class CoreQuantityOperation<Q extends Quantity>
{
private final CollectionComplianceTests aTests =
new CollectionComplianceTests();
public Collection<ICommand<IQuantityCollection<Q>>> actionsFor(
List<IQuantityCollection<Q>> selection, IStore destination,
IContext context)
{
Collection<ICommand<IQuantityCollection<Q>>> res =
new ArrayList<ICommand<IQuantityCollection<Q>>>();
if (appliesTo(selection))
{
// so, do we do our indexed commands?
if (getATests().allEqualLengthOrSingleton(selection))
{
addIndexedCommands(selection, destination, res, context);
}
// aah, what about temporal (interpolated) values?
if (getATests().allTemporal(selection) && getATests()
.suitableForTimeInterpolation(selection)
|| getATests().hasTemporal(selection) && getATests()
.allEqualLengthOrSingleton(selection))
{
addInterpolatedCommands(selection, destination, res, context);
}
}
return res;
}
protected ITemporalQuantityCollection<Q> getLongestTemporalCollections(
List<IQuantityCollection<Q>> selection)
{
// find the longest time series.
Iterator<IQuantityCollection<Q>> iter = selection.iterator();
ITemporalQuantityCollection<Q> longest = null;
while (iter.hasNext())
{
IQuantityCollection<Q> thisQ = iter.next();
if (thisQ.isTemporal())
{
ITemporalQuantityCollection<Q> thisC =
(ITemporalQuantityCollection<Q>) thisQ;
if (longest == null)
{
longest = thisC;
}
else
{
// store the longest one
longest = thisC.getValuesCount() > longest.getValuesCount() ? thisC : longest;
}
}
}
return longest;
}
/**
* determine if this dataset is suitable
*
* @param selection
* @return
*/
protected abstract boolean appliesTo(List<IQuantityCollection<Q>> selection);
/**
* produce any new commands for this s election
*
* @param selection
* current selection
* @param destination
* where the results will end up
* @param commands
* the list of commands
*/
protected abstract void addIndexedCommands(
List<IQuantityCollection<Q>> selection, IStore destination,
Collection<ICommand<IQuantityCollection<Q>>> commands, IContext context);
/**
* add any commands that require temporal interpolation
*
* @param selection
* @param destination
* @param res
*/
protected abstract void addInterpolatedCommands(
List<IQuantityCollection<Q>> selection, IStore destination,
Collection<ICommand<IQuantityCollection<Q>>> res, IContext context);
public CollectionComplianceTests getATests()
{
return aTests;
}
/**
* the command that actually produces data
*
* @author ian
*
*/
public abstract class CoreQuantityCommand extends
AbstractCommand<IQuantityCollection<Q>>
{
private final ITemporalQuantityCollection<Q> timeProvider;
public CoreQuantityCommand(String title, String description, IStore store,
boolean canUndo, boolean canRedo, List<IQuantityCollection<Q>> inputs,
IContext context)
{
this(title, description, store, canUndo, canRedo, inputs, null, context);
}
public CoreQuantityCommand(String title, String description, IStore store,
boolean canUndo, boolean canRedo, List<IQuantityCollection<Q>> inputs,
ITemporalQuantityCollection<Q> timeProvider, IContext context)
{
super(title, description, store, canUndo, canRedo, inputs, context);
this.timeProvider = timeProvider;
}
/**
* empty the contents of any results collections
*
* @param outputs
*/
private void clearOutputs(List<IQuantityCollection<Q>> outputs)
{
// clear out the lists, first
Iterator<IQuantityCollection<Q>> iter = outputs.iterator();
while (iter.hasNext())
{
IQuantityCollection<Q> qC = (IQuantityCollection<Q>) iter.next();
qC.clearQuiet();
}
}
/**
* wrap the actual operation. We're doing this since we need to separate it from the core
* "execute" operation in order to support dynamic updates
*
* @param unit
* the units to use
* @param outputs
* the list of output series
*/
protected void performCalc(Unit<Q> unit,
List<IQuantityCollection<Q>> outputs)
{
IQuantityCollection<Q> target = outputs.iterator().next();
clearOutputs(outputs);
if (timeProvider != null)
{
Collection<Long> times = timeProvider.getTimes();
Iterator<Long> iter = times.iterator();
while (iter.hasNext())
{
long thisT = (long) iter.next();
Double val = calcThisInterpolatedElement(thisT);
if (val != null)
{
storeTemporalValue(target, thisT, val);
}
}
}
else
{
int numItems = numElements();
for (int elementCount = 0; elementCount < numItems; elementCount++)
{
Double thisResult = calcThisElement(elementCount);
// ok, done - store it!
storeValue(target, elementCount, thisResult);
}
}
// and fire out the update
target.fireDataChanged();
}
protected int numElements()
{
int res = 0;
// we may have a singleton array. select the non singleton array
Iterator<IQuantityCollection<Q>> iter = getInputs().iterator();
while (iter.hasNext())
{
IQuantityCollection<Q> iQuantityCollection =
(IQuantityCollection<Q>) iter.next();
int thisSize = iQuantityCollection.getValuesCount();
res = Math.max(res, thisSize);
}
return res;
}
private void storeTemporalValue(IQuantityCollection<Q> target, long thisT,
double val)
{
ITemporalQuantityCollection<Q> qc =
(ITemporalQuantityCollection<Q>) target;
qc.add(thisT, Measure.valueOf(val, determineOutputUnit(target)));
}
/**
* store this value into the target (optionally including temporal aspects)
*
* @param target
* destination
* @param count
* index for this value
* @param value
* the value to store
*/
private void storeValue(IQuantityCollection<Q> target, int count,
Double value)
{
if (target.isTemporal())
{
// ok, the input and output arrays must be temporal.
ITemporalQuantityCollection<Q> qc =
(ITemporalQuantityCollection<Q>) target;
ITemporalQuantityCollection<Q> qi =
(ITemporalQuantityCollection<Q>) getInputs().get(0);
Long[] timeData = qi.getTimes().toArray(new Long[]
{});
qc.add(timeData[count],
Measure.valueOf(value, determineOutputUnit(target)));
}
else
{
target.add(Measure.valueOf(value, determineOutputUnit(target)));
}
}
/**
* produce a calculated value for the relevant index of the first input collection
*
* @param elementCount
* @return
*/
protected abstract Double calcThisElement(int elementCount);
/**
* produce a calculated value for the relevant index of the first input collection
*
* @param elementCount
* @return
*/
protected abstract Double calcThisInterpolatedElement(long time);
@Override
protected void recalculate(IStoreItem subject)
{
// get the unit
IQuantityCollection<Q> first = getInputs().get(0);
Unit<Q> unit = determineOutputUnit(first);
// update the results
performCalc(unit, getOutputs());
}
/**
* produce a target of the correct type
*
* @param input
* one of the input series
* @param unit
* the units to use
* @return
*/
protected IQuantityCollection<Q> createQuantityTarget(
IQuantityCollection<Q> input, Unit<Q> unit)
{
// double check the name is ok
final String outName = getOutputName();
IQuantityCollection<Q> target = null;
if (outName != null)
{
if (timeProvider != null)
{
target = new TemporalQuantityCollection<Q>(outName, this, unit);
}
else
{
target = new QuantityCollection<Q>(outName, this, unit);
}
}
return target;
}
@Override
public void execute()
{
// get the unit
IQuantityCollection<Q> first = getInputs().get(0);
List<IQuantityCollection<Q>> outputs =
new ArrayList<IQuantityCollection<Q>>();
// sort out the output unit
Unit<Q> unit = determineOutputUnit(first);
// ok, generate the new series
final IQuantityCollection<Q> target = createQuantityTarget(first, unit);
if (target == null)
{
getContext().logError(IContext.Status.WARNING,
"User cancelled create operation", null);
return;
}
outputs.add(target);
// store the output
super.addOutput(target);
// start adding values.
performCalc(unit, outputs);
// tell each series that we're a dependent
Iterator<IQuantityCollection<Q>> iter = getInputs().iterator();
while (iter.hasNext())
{
ICollection iCollection = iter.next();
iCollection.addDependent(this);
}
// ok, done
getStore().add(target);
}
protected Unit<Q> determineOutputUnit(IQuantityCollection<Q> first)
{
return first.getUnits();
}
}
}