
net.sf.jagg.Aggregator Maven / Gradle / Ivy
Show all versions of jagg-core Show documentation
package net.sf.jagg;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import net.sf.jagg.exception.AggregatorCreationException;
import net.sf.jagg.exception.ParseException;
import net.sf.jagg.exception.PropertyAccessException;
import net.sf.jagg.util.AggregatorCache;
import net.sf.jagg.util.MethodCache;
import net.sf.jagg.math.DoubleDouble;
/**
* This abstract class allows for the state necessary to implement aggregate
* functions. Subclasses define the following steps of the Aggregation
* algorithm:
*
* - Initialization, with the
init
method. This initializes
* the state of the Aggregator. Aggregators
may be reused, so
* this method must be prepared to instantiate or reset any state objects it
* maintains.
* - Iteration, with the
iterate
method. This adds a value to
* the aggregation. This will be called exactly once per object to
* aggregate.
* - Merging, with the
merge
method. In parallel execution,
* this merges results from two Aggregator
objects resulting
* from parallel execution. After the merge
method completes,
* then this Aggregator
reflects the combined state of both
* this Aggregator
and another Aggregator
. Merging
* takes place during parallel execution and during super aggregation
* (rollups, cubes, and grouping sets).
* - Termination, with the
terminate
method. At this point, all
* aggregation is complete, and only a final result needs to be constructed.
*
*
* The factory method getAggregator
creates
* AggregateFunctions
and marks them as in use. They are stored
* in a cache (a HashMap
) so they may be reused. After an
* AggregateFunction
is used, it will be marked as not in use; it
* remains in the cache and it may be reused.
*
* The abstract method replicate
must be defined for every
* Aggregator
. This method returns an uninitialized copy of the
* Aggregator
, with the same type and the same properties to
* analyze as the original Aggregator
.
*
* The concrete method terminateDoubleDouble
may be overridden
* by Aggregators
that operate on floating-point numbers. This
* allows other Aggregators
to use the high-precision result, a
* DoubleDouble
, internally in their calculations. The default
* implementation simply returns DoubleDouble.NaN
.
*
* Currently, AggregateFunctions
do not need to be thread-safe.
* The Aggregation
class is the only class that uses
* AggregateFunctions
, and only one Thread
at a time
* uses any AggregateFunction
.
*
* However, internally, the Aggregator
class uses synchronized
* access to internal HashMaps
to cache AggregateFunction
* (and Methods
).
*
* The {@link #getValueFromProperty(Object, String) getValueProperty} method
* has been made public
as of version 0.7.2.
*
* @see net.sf.jagg.math.DoubleDouble
*
* @author Randy Gettman
* @since 0.1.0
*/
public abstract class Aggregator implements AggregateFunction
{
/**
* Special pseudo-property indicating that the object itself is to be
* aggregated, instead of a property of the object.
*/
public static final String PROP_SELF = ".";
// Cache Method objects to save on instantiation/garbage collection costs.
private static final MethodCache myMethodCache = MethodCache.getMethodCache();
// Cache Aggregator objects to save on instantiation/garbage collection
// costs. The key is the aggregator specification string.
private static final AggregatorCache myAggregatorCache = new AggregatorCache();
private String myProperty;
private boolean amIInUse = false;
/**
* Default constructor is protected so that only subclasses of
* Aggregator
can be instantiated.
*/
protected Aggregator() {}
/**
* Adds the given AggregateFunction
to an internal cache. If
* it's not in use, then it marks it as "in use" and returns it. Else, it
* searches the cache for an AggregateFunction
that matches the
* given AggregateFunction
and is not already in use. If none
* exist in the cache, then it replicates the given
* AggregateFunction
, adds it to the cache, and returns it.
*
* @param archetype The AggregateFunction
whose properties (and
* type) need to be matched.
* @return A matching AggregateFunction
object. It could be
* archetype
itself if it's not already in use, or it could
* be null
if archetype
was null.
*/
public static AggregateFunction getAggregator(AggregateFunction archetype)
{
return myAggregatorCache.getFunction(archetype);
}
/**
* Creates an AggregateFunction
based on an aggregator
* specification string. Does not mark it as in use. Does not add it
* to the internal cache. This is meant to aid the caller in creating an
* AggregateFunction
based on the following specification
* string format: aggName(property/-ies)
.
* This assumes that the desired AggregateFunction
has a
* one-argument constructor with a String
argument for its
* property or properties.
*
* @param aggSpec The String specification of an AggregateFunction
.
* @return An AggregateFunction
object.
* @throws ParseException If the aggregator specification was mal-
* formed.
* @throws AggregatorCreationException If there was a problem instantiating
* the AggregateFunction
.
*/
public static AggregateFunction getAggregator(String aggSpec)
{
int leftParenIdx = aggSpec.indexOf("(");
int rightParenIdx = aggSpec.lastIndexOf(")");
if (leftParenIdx == -1 || rightParenIdx == -1 || leftParenIdx > rightParenIdx)
throw new ParseException("Malformed Aggregator specification: " + aggSpec);
String aggName = aggSpec.substring(0, leftParenIdx);
// int dotIndex = aggSpec.indexOf(".");
// // Any dot past a "(" isn't a package specifier; it could be part of an argument.
// if (dotIndex == -1 || dotIndex > leftParenIdx)
// {
// aggName = Aggregator.class.getPackage().getName() + "." + aggName;
// }
if (aggName.indexOf(".") == -1)
aggName = Aggregator.class.getPackage().getName() + "." + aggName;
if (!aggName.endsWith("Aggregator"))
aggName = aggName + "Aggregator";
String property = aggSpec.substring(leftParenIdx + 1, rightParenIdx);
try
{
Class aggClass = Class.forName(aggName);
Constructor ctor = aggClass.getConstructor(String.class);
return (AggregateFunction) ctor.newInstance(property);
}
catch (ClassNotFoundException e)
{
throw new AggregatorCreationException("Unknown AggregateFunction class \"" + aggName + "\".", e);
}
catch (NoSuchMethodException e)
{
throw new AggregatorCreationException("Can't find constructor for AggregateFunction class \"" +
aggName + "\" that contains exactly one String parameter.", e);
}
catch (InstantiationException e)
{
throw new AggregatorCreationException("AggregateFunction specified is not a concrete class: \"" +
aggName + "\".", e);
}
catch (IllegalAccessException e)
{
throw new AggregatorCreationException("Unable to construct AggregateFunction \"" +
aggName + "\".", e);
}
catch (InvocationTargetException e)
{
throw new AggregatorCreationException("Exception caught instantiating AggregateFunction \"" +
aggName + "\": " + e.getCause().getClass().getName(), e);
}
catch (ClassCastException e)
{
throw new AggregatorCreationException("Class found is not an AggregateFunction: \"" +
aggName + "\".", e);
}
}
/**
* Gets a specific Method
from an internal cache, or creates it
* using reflection if it does not exist. The method is looked up via the
* name "get<Property>", with the given property name, on the given
* value object. Invokes the Method
and returns the value.
* This is expected to be called in the iterate
method, so that
* it can access the object's property, although it can be called from any
* AggregateFunction
method. This method is here to standardize
* the "bean" method naming convention specified by AggregateFunction
* and to cache Method
objects for internal use.
*
* @param value The object on which to lookup a property value.
* @param property The property to lookup.
* @return The object's property value.
* @throws PropertyAccessException If the desired Method
* does not exist, if the Method
cannot be invoked because
* of Java language access control (e.g. private, etc.), or if the
* invoked Method
throws an Exception
.
* @see #iterate
*/
public static Object getValueFromProperty(Object value, String property)
{
return myMethodCache.getValueFromProperty(value, property);
}
/**
* Sets the property name. Subclasses may override this method if they
* want to extract more information from the property string, e.g.
* "Name(property, addlInfo)". The default implementation simply stores the
* entire string to be made available via "getProperty".
*
* @param property The property name.
* @see #getProperty()
*/
protected void setProperty(String property)
{
myProperty = property;
}
/**
* Determines whether this Aggregator
is in use.
* @return A boolean indicating whether it's in use.
*/
public final boolean isInUse()
{
return amIInUse;
}
/**
* Sets whether this Aggregator
is in use.
* @param inUse A boolean indicating whether it's in use.
*/
public final void setInUse(boolean inUse)
{
amIInUse = inUse;
}
/**
* Returns an uninitialized copy of this AggregateFunction
object,
* with the same property(ies) to analyze.
* @return An uninitialized copy of this AggregateFunction
object.
*/
public abstract AggregateFunction replicate();
/**
* Initializes the Aggregator
. Subclasses should override this
* method to instantiate state objects that will hold the state of the
* aggregation. E.g., a "sum" aggregation will initialize a sum object to
* zero. This Aggregator
may be reused, so any objects may
* already be instantiated, but their state must be reset.
*/
public abstract void init();
/**
* Processes the given value into the aggregation. E.g., a "sum"
* aggregation will add this object's property value to a sum object. An
* implementation will likely want to call getValueFromProperty
,
* which accesses a cache of Methods
to find the property's
* value in the given object.
*
* @param value The value to aggregate.
* @see #getValueFromProperty
*/
public abstract void iterate(Object value);
/**
* Merges the state of the given Aggregator
into this own
* Aggregator
's state. Called when parallel execution
* yields more than one Aggregator
to combine into one.
*
* @param agg The AggregateFunction
whose state needs to be
* merged into this one.
*/
public abstract void merge(AggregateFunction agg);
/**
* At this point the aggregation of values is complete, and a final result
* needs to be constructed. This method constructs that final result.
*
* @return A value representing the result of the aggregation.
*/
public abstract Object terminate();
/**
* Return the result as a DoubleDouble
. This is used mainly
* when other Aggregators
that use this result must maintain a
* high precision.
* @return A DoubleDouble
representing the result of the
* aggregation. The default implementation returns
* DoubleDouble.NaN
.
* @see DoubleDouble
* @since 0.4.0
*/
public DoubleDouble terminateDoubleDouble()
{
return DoubleDouble.NaN;
}
/**
* Determines whether the given Aggregator
is equivalent to
* this Aggregator
. This is necessary because
* Aggregator
objects will be stored in a HashMap
.
*
* @param o Another Aggregator
.
* @return true
if equivalent, false
otherwise.
*/
public boolean equals(Object o)
{
return (getClass().equals(o.getClass()) && toString().equals(o.toString()));
}
/**
* Calculates a hash code for this Aggregator
. This is
* necessary because Aggregator
objects will be stored in a
* HashMap
.
*
* @return The hash code of this Aggregator
. It is computed by
* taking the hash of the result of the toString
method.
* @see #toString
*/
public int hashCode()
{
return toString().hashCode();
}
/**
* Retrieves the property that this Aggregator
aggregates.
*
* @return A property name.
*/
public String getProperty()
{
return myProperty;
}
/**
* A String representation of this Aggregator
, in the form
* "className(property)".
*
* @return The String representation.
*/
public String toString()
{
return getClass().getName() + "(" + getProperty() + ")";
}
}