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

net.sf.jagg.Aggregator Maven / Gradle / Ivy

Go to download

jAgg is a Java 5.0 API that supports “group by” operations on Lists of Java objects: aggregate operations such as count, sum, max, min, avg, and many more. It also allows custom aggregate operations.

The newest version!
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:
 * 
    *
  1. 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.
  2. *
  3. Iteration, with the iterate method. This adds a value to * the aggregation. This will be called exactly once per object to * aggregate.
  4. *
  5. 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).
  6. *
  7. Termination, with the terminate method. At this point, all * aggregation is complete, and only a final result needs to be constructed.
  8. *
* *

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() + ")"; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy