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

net.sf.jagg.AnalyticAggregator 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.sf.jagg.exception.ParseException;
import net.sf.jagg.exception.AnalyticCreationException;
import net.sf.jagg.util.AnalyticCache;
import net.sf.jagg.model.AnalyticValue;
import net.sf.jagg.model.AnalyticContext;
import net.sf.jagg.model.OrderByClause;
import net.sf.jagg.model.OrderByElement;
import net.sf.jagg.model.PartitionClause;
import net.sf.jagg.model.WindowClause;

/**
 * An AnalyticAggregator is not used directly.  jAgg creates an
 * AnalyticAggregator for every AnalyticFunction.  In
 * addition to delegating analytic operations to an AnalyticFunction,
 * this object also carries partition data, order-by data, and windowing data.
 *
 * @author Randy Gettman
 * @since 0.9.0
 */
public class AnalyticAggregator implements AnalyticFunction
{
   private static final boolean DEBUG = false;

   // Cache AnalyticAggregator objects to save on instantiation/garbage collection
   // costs.  The key is the analytic specification string.
   private static final AnalyticCache myAnalyticCache = new AnalyticCache();

   /**
    * The part of the analytic specification string that indicates the
    * partition-by clause.
    */
   public static final String PARTITION_CLAUSE_IND = "partitionBy";
   /**
    * The part of the analytic specification string that indicates the
    * partition-by clause.
    */
   public static final String ORDER_BY_CLAUSE_IND = "orderBy";
   /**
    * The part of the analytic specification string that indicates the
    * window clause by rows.
    */
   public static final String WINDOW_CLAUSE_IND_ROWS = "rows";
   /**
    * The part of the analytic specification string that indicates the
    * window clause by a range of values.
    */
   public static final String WINDOW_CLAUSE_IND_RANGE = "range";

   /**
    * Sort ascending (default).
    */
   public static final String ASC = "ASC";
   /**
    * Sort descending.
    */
   public static final String DESC = "DESC";
   /**
    * Use this to indicate which sequence nulls should be ordered.
    */
   public static final String NULLS = "NULLS";
   /**
    * Sort nulls first (default if descending order).
    */
   public static final String FIRST = "FIRST";
   /**
    * Sort nulls last (default if ascending order).
    */
   public static final String LAST = "LAST";
   /**
    * The window clause's keyword for indicating a range of values or rows.
    */
   public static final String WINDOW_THROUGH = ",";

   /**
    * Legal expected suffixes:
    * 
    *
  • AnalyticAggregator - So that a subclass of an existing * Aggregator can supply the needed analytic functionality.
  • *
  • Aggregator - So that existing Aggregators can also * exist as AnalyticFunctions.
  • *
  • Analytic - So that an AnalyticFunction that isn't * valid as an Aggregtor can be created.
  • *
* * Examples: *
    *
  • MaxAnalyticAggregator - It's a subclass of MaxAggregator * that contains additional logic to support analytic functionality as an * AnalyticFunction.
  • *
  • SumAggregator - It's an AggregateFunction that doubles as * an AnalyticFunction.
  • *
  • LagAnalytic - It's an AnalyticFunction that is not * intended for use as an AggregateFunction.
  • *
*/ private static final List ANALYTIC_SUFFIXES = Arrays.asList("AnalyticAggregator", "Aggregator", "Analytic"); private AnalyticFunction myAnalyticFunction; private PartitionClause myPartition; private OrderByClause myOrderBy; private WindowClause myWindow; /** * Private constructor to ensure that the "Builder" pattern is used. * @param builder A Builder. */ private AnalyticAggregator(Builder builder) { if (builder.myAnalyticFunction == null) throw new IllegalArgumentException("Analytic function not supplied!"); myAnalyticFunction = builder.myAnalyticFunction; myPartition = builder.myPartition; myOrderBy = builder.myOrderBy; myWindow = builder.myWindow; } /** * Private copy constructor. Replicates the internal * AnalyticFunction, but copies the references to everything * else. * @param agg The AnalyticAggregator to copy. */ private AnalyticAggregator(AnalyticAggregator agg) { myAnalyticFunction = agg.replicate(); myPartition = agg.myPartition; myOrderBy = agg.myOrderBy; myWindow = agg.myWindow; } /** * Creates an AnalyticFunction based on an analytic * 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 * AnalyticFunction based on the following analytic * specification string format: * anaName(property/-ies) [partitionBy(property/-ies)] * [orderBy([prop [ASC|DESC] [NULLS [FIRST|LAST]] ]*)] * [rows|range([start] through [end])]. * This assumes that the desired AnalyticFunction has a * one-argument constructor with a String argument for its * property or properties. * * @param anaSpec The String specification of an AnalyticFunction. * @return An AnalyticAggregator object that encapsulates the * AnalyticFunction and any PartitionClause, * OrderByClause, and WindowClause. * @throws ParseException If the analytic specification was mal- * formed. */ public static AnalyticAggregator getAnalytic(String anaSpec) { int partitionIdx = anaSpec.indexOf(PARTITION_CLAUSE_IND); int orderByIdx = anaSpec.indexOf(ORDER_BY_CLAUSE_IND); int windowRowsIdx = anaSpec.indexOf(WINDOW_CLAUSE_IND_ROWS); int windowRangeIdx = anaSpec.indexOf(WINDOW_CLAUSE_IND_RANGE); if (windowRowsIdx != -1 && windowRangeIdx != -1) { throw new ParseException("Can't specify both " + WINDOW_CLAUSE_IND_ROWS + " and " + WINDOW_CLAUSE_IND_RANGE + " in an analytic function."); } int minAnalyticIdx = -1; if (partitionIdx != -1) { minAnalyticIdx = partitionIdx; } if (orderByIdx != -1 && (minAnalyticIdx == -1 || orderByIdx < minAnalyticIdx)) { minAnalyticIdx = orderByIdx; } if (windowRowsIdx != -1 && (minAnalyticIdx == -1 || windowRowsIdx < minAnalyticIdx)) { minAnalyticIdx = windowRowsIdx; } if (windowRangeIdx != -1 && (minAnalyticIdx == -1 || windowRangeIdx < minAnalyticIdx)) { minAnalyticIdx = windowRangeIdx; } String aggSpecPart; if (minAnalyticIdx != -1) { aggSpecPart = anaSpec.substring(0, minAnalyticIdx).trim(); } else { aggSpecPart = anaSpec; } int leftParenIdx = aggSpecPart.indexOf("("); int rightParenIdx = aggSpecPart.lastIndexOf(")"); if (leftParenIdx == -1 || rightParenIdx == -1 || leftParenIdx > rightParenIdx) throw new ParseException("Malformed Analytic specification: " + anaSpec); String aggName = aggSpecPart.substring(0, leftParenIdx); int dotIndex = aggSpecPart.indexOf("."); // Any dot past a "(" isn't a package specifier; it could be part of an argument. if (dotIndex == -1 || dotIndex > leftParenIdx) { aggName = AnalyticFunction.class.getPackage().getName() + "." + aggName; } if (DEBUG) { System.out.println("AnaAgg.getAna: aggName = \"" + aggName + "\"."); } AnalyticFunction anaFunc = null; // Loop through legal expected suffixes for analytic function classes. for (String suffix : ANALYTIC_SUFFIXES) { String aggNameWithSuffix = aggName; if (!aggName.endsWith(suffix)) aggNameWithSuffix = aggName + suffix; String property = aggSpecPart.substring(leftParenIdx + 1, rightParenIdx); try { Class aggClass = Class.forName(aggNameWithSuffix); Constructor ctor = aggClass.getConstructor(String.class); anaFunc = (AnalyticFunction) ctor.newInstance(property); break; } catch (ClassNotFoundException ignored) { // Continue on through list of suffixes } catch (NoSuchMethodException e) { throw new AnalyticCreationException("Can't find constructor for AnalyticFunction class \"" + aggName + "\" that contains exactly one String parameter.", e); } catch (InstantiationException e) { throw new AnalyticCreationException("AnalyticFunction specified is not a concrete class: \"" + aggName + "\".", e); } catch (IllegalAccessException e) { throw new AnalyticCreationException("Unable to construct AnalyticFunction \"" + aggName + "\".", e); } catch (InvocationTargetException e) { throw new AnalyticCreationException("Exception caught instantiating AnalyticFunction \"" + aggName + "\": " + e.getCause().getClass().getName(), e); } catch (ClassCastException e) { throw new AnalyticCreationException("Class found is not an AnalyticFunction: \"" + aggName + "\".", e); } } if (anaFunc == null) throw new AnalyticCreationException("Unknown AnalyticFunction class \"" + aggName + "\"."); Builder builder = new Builder(); builder.setAnalyticFunction(anaFunc) .setPartition(getPartitionClause(anaSpec, partitionIdx, orderByIdx, windowRowsIdx, windowRangeIdx)) .setOrderBy(getOrderByClause(anaSpec, partitionIdx, orderByIdx, windowRowsIdx, windowRangeIdx)) .setWindow(getWindowClause(anaSpec, partitionIdx, orderByIdx, windowRowsIdx, windowRangeIdx)); return builder.build(); } /** * Adds the given AnalyticAggregator 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 AnalyticAggregator that matches the * given AnalyticAggregator and is not already in use. If none * exist in the cache, then it replicates the given * AnalyticAggregator, adds it to the cache, and returns it. * * @param archetype The AnalyticAggregator whose properties (and * type) need to be matched. * @return A matching AnalyticAggregator object. It could be * archetype itself if it's not already in use, or it could * be null if archetype was null. */ public static AnalyticAggregator getAnalyticAggregator(AnalyticAggregator archetype) { return myAnalyticCache.getFunction(archetype); } /** * Creates a PartitionClause out of any part of the given * analytic specification string that may define a partition-by clause. * @param anaSpec The analytic specification string. * @param partitionIdx The already calculated index of PARTITION_CLAUSE_IND. * @param orderByIdx The already calculated index of ORDER_BY_CLAUSE_IND. * @param windowRowsIdx The already calculated index of WINDOW_CLAUSE_IND_ROWS. * @param windowRangeIdx The already calculated index of WINDOW_CLAUSE_IND_RANGE. * @return A PartitionClause, or null if the * clause is absent. * @throws ParseException If the partition-by clause is malformed. */ private static PartitionClause getPartitionClause(String anaSpec, int partitionIdx, int orderByIdx, int windowRowsIdx, int windowRangeIdx) { if (partitionIdx == -1) return null; int endOfPartitionByClause = -1; if (orderByIdx > partitionIdx) { endOfPartitionByClause = orderByIdx; } if (windowRowsIdx > partitionIdx && windowRowsIdx < endOfPartitionByClause) { endOfPartitionByClause = windowRowsIdx; } if (windowRangeIdx > partitionIdx && windowRangeIdx < endOfPartitionByClause) { endOfPartitionByClause = windowRangeIdx; } String partitionByClause; if (endOfPartitionByClause != -1) { partitionByClause = anaSpec.substring(partitionIdx, endOfPartitionByClause).trim(); } else { partitionByClause = anaSpec.substring(partitionIdx).trim(); } // partitionBy(prop[, prop]*) int leftParenIdx = partitionByClause.indexOf("("); int rightParenIdx = partitionByClause.lastIndexOf(")"); if (leftParenIdx == -1 || rightParenIdx == -1 || leftParenIdx > rightParenIdx) throw new ParseException("Malformed Analytic " + PARTITION_CLAUSE_IND + " clause: " + anaSpec); String inParens = partitionByClause.substring(leftParenIdx + 1, rightParenIdx); List propsList = new ArrayList(); if (inParens.trim().length() > 0) { String[] properties = inParens.split(","); for (String prop : properties) { propsList.add(prop.trim()); } } return new PartitionClause(propsList); } /** * Creates an OrderByClause out of any part of the given * analytic specification string that may define an order-by clause. * @param anaSpec The analytic specification string. * @param partitionIdx The already calculated index of PARTITION_CLAUSE_IND. * @param orderByIdx The already calculated index of ORDER_BY_CLAUSE_IND. * @param windowRowsIdx The already calculated index of WINDOW_CLAUSE_IND_ROWS. * @param windowRangeIdx The already calculated index of WINDOW_CLAUSE_IND_RANGE. * @return An OrderByClause, or null if the * clause is absent. * @throws ParseException If the order-by clause is malformed. */ private static OrderByClause getOrderByClause(String anaSpec, int partitionIdx, int orderByIdx, int windowRowsIdx, int windowRangeIdx) { if (orderByIdx == -1) return null; int endOfOrderByClause = -1; if (partitionIdx > orderByIdx) { endOfOrderByClause = partitionIdx; } if (windowRowsIdx > orderByIdx && (windowRowsIdx < endOfOrderByClause || endOfOrderByClause == -1)) { endOfOrderByClause = windowRowsIdx; } if (windowRangeIdx > orderByIdx && (windowRangeIdx < endOfOrderByClause || endOfOrderByClause == -1)) { endOfOrderByClause = windowRangeIdx; } String orderByClause; if (endOfOrderByClause != -1) { orderByClause = anaSpec.substring(orderByIdx, endOfOrderByClause).trim(); } else { orderByClause = anaSpec.substring(orderByIdx).trim(); } // orderBy(orderByProp[, orderByProp]*) // orderByProp: prop [ASC|DESC] [NULLS [FIRST|LAST]] int leftParenIdx = orderByClause.indexOf("("); int rightParenIdx = orderByClause.lastIndexOf(")"); if (leftParenIdx == -1 || rightParenIdx == -1 || leftParenIdx > rightParenIdx) throw new ParseException("Malformed Analytic " + ORDER_BY_CLAUSE_IND + " clause: " + anaSpec); String inParens = orderByClause.substring(leftParenIdx + 1, rightParenIdx); List elementsList = new ArrayList(); if (inParens.trim().length() > 0) { String[] orderBySpecs = inParens.split(","); for (String orderBySpec : orderBySpecs) { String[] parts = orderBySpec.trim().split("\\s+"); String property; OrderByElement.SortDir ordering; OrderByElement.NullSort nullOrdering; if (parts.length > 0 && parts.length < 5) { property = parts[0]; ordering = OrderByElement.SortDir.ASC; nullOrdering = OrderByElement.NullSort.LAST; if (parts.length == 2 || parts.length == 4) { // ordering is next. if (ASC.equalsIgnoreCase(parts[1])) { ordering = OrderByElement.SortDir.ASC; nullOrdering = OrderByElement.NullSort.LAST; } else if (DESC.equalsIgnoreCase(parts[1])) { ordering = OrderByElement.SortDir.DESC; nullOrdering = OrderByElement.NullSort.FIRST; } else if (NULLS.equalsIgnoreCase(parts[1])) throw new ParseException("OrderBy: Expected \"" + FIRST + "\" or \"" + LAST + " after " + NULLS + ": " + orderBySpec); else throw new ParseException("OrderBy: Expected \"" + ASC + "\" or \"" + DESC + ": " + orderBySpec); } if (parts.length == 3 || parts.length == 4) { if (!NULLS.equalsIgnoreCase(parts[parts.length - 2])) throw new ParseException("OrderBy: Expected \"" + NULLS + " " + FIRST + "|" + LAST + ": " + orderBySpec); if (LAST.equalsIgnoreCase(parts[parts.length - 1])) nullOrdering = OrderByElement.NullSort.LAST; else if (FIRST.equalsIgnoreCase(parts[parts.length - 1])) nullOrdering = OrderByElement.NullSort.FIRST; else throw new ParseException("OrderBy: Expected \"" + FIRST + "\" or \"" + LAST + ": " + orderBySpec); } } else { throw new ParseException("OrderBy: Expected \"property\" [" + ASC + "|" + DESC + "] [" + NULLS + " ]" + FIRST + "|" + LAST + ": " + orderBySpec); } elementsList.add(new OrderByElement(property, ordering, nullOrdering)); } } return new OrderByClause(elementsList); } /** * Creates a WindowClause out of any part of the given * analytic specification string that may define a window clause. * @param anaSpec The analytic specification string. * @param partitionIdx The already calculated index of PARTITION_CLAUSE_IND. * @param orderByIdx The already calculated index of ORDER_BY_CLAUSE_IND. * @param windowRowsIdx The already calculated index of WINDOW_CLAUSE_IND_ROWS. * @param windowRangeIdx The already calculated index of WINDOW_CLAUSE_IND_RANGE. * @return A WindowClause, or null if the * clause is absent. * @throws ParseException If the window clause is malformed. */ private static WindowClause getWindowClause(String anaSpec, int partitionIdx, int orderByIdx, int windowRowsIdx, int windowRangeIdx) { if (windowRowsIdx == -1 && windowRangeIdx == -1) return null; if (windowRowsIdx != -1 && windowRangeIdx != -1) throw new ParseException("Can't have " + WINDOW_CLAUSE_IND_RANGE + " and " + WINDOW_CLAUSE_IND_ROWS + " at the same time: " + anaSpec); WindowClause.Type windowType; int startOfWindowClause; int endOfWindowClause; if (windowRowsIdx != -1) { windowType = WindowClause.Type.ROWS; startOfWindowClause = windowRowsIdx; endOfWindowClause = -1; if (orderByIdx > windowRowsIdx) { endOfWindowClause = orderByIdx; } if (partitionIdx > windowRowsIdx && partitionIdx < endOfWindowClause) { endOfWindowClause = partitionIdx; } } else //if (windowRangeIdx != -1) { windowType = WindowClause.Type.RANGE; startOfWindowClause = windowRangeIdx; endOfWindowClause = -1; if (orderByIdx > windowRangeIdx) { endOfWindowClause = orderByIdx; } if (partitionIdx > windowRangeIdx && partitionIdx < endOfWindowClause) { endOfWindowClause = partitionIdx; } } String windowClause; if (endOfWindowClause != -1) { windowClause = anaSpec.substring(startOfWindowClause, endOfWindowClause).trim(); } else { windowClause = anaSpec.substring(startOfWindowClause).trim(); } // rows|range([value] through [value]) int leftParenIdx = windowClause.indexOf("("); int rightParenIdx = windowClause.lastIndexOf(")"); if (leftParenIdx == -1 || rightParenIdx == -1 || leftParenIdx > rightParenIdx) throw new ParseException("Malformed Analytic window clause: " + anaSpec); String inParens = windowClause.substring(leftParenIdx + 1, rightParenIdx).trim(); if (inParens.length() == 0) { return new WindowClause(windowType, null, null); } else { try { int index = inParens.indexOf(WINDOW_THROUGH); if (index != -1) { String before = inParens.substring(0, index).trim(); String after = inParens.substring(index + 1).trim(); Double dBefore = (before.equals("") ? null : Double.valueOf(before)); Double dAfter = (after.equals("") ? null : Double.valueOf(after)); return new WindowClause(windowType, dBefore, dAfter); } else if (inParens.trim().equals("")) { return new WindowClause(windowType, null, null); } } catch (NumberFormatException e) { throw new ParseException("Window clause arguments must be numbers: " + anaSpec, e); } } // If we got here, then the window clause values were illegal. throw new ParseException("Window clause arguments must be ([value] " + WINDOW_THROUGH + " [value]) or ():" + anaSpec); } /** * Delegate to the analytic function. * @return The property's value. */ public String getProperty() { return myAnalyticFunction.getProperty(); } /** * Delegate to the analytic function. */ public void init() { myAnalyticFunction.init(); } /** * Delegate to the analytic function. * @value The value to iterate. */ public void iterate(Object value) { myAnalyticFunction.iterate(value); } /** * Delegate to the analytic function. * @param func The AggregateFunction to merge into this one. */ public void merge(AggregateFunction func) { myAnalyticFunction.merge(func); } /** * Delegate to the analytic function. * @value The value to delete. */ public void delete(Object value) { myAnalyticFunction.delete(value); } /** * Delegate to the analytic function. * @return Whether this AnalyticFunction can operate with a * window clause. */ public boolean takesWindowClause() { return myAnalyticFunction.takesWindowClause(); } /** * Delegate to the analytic function. * @return The supplied WindowClause, if any. */ public WindowClause getWindowClause() { return myAnalyticFunction.getWindowClause(); } /** * Delegate to the analytic function. * @return A value representing the result of the analytic calculation. */ public Object terminate() { return myAnalyticFunction.terminate(); } /** * Delegate to the analytic function. * @param inUse Whether it's in use. */ public void setInUse(boolean inUse) { myAnalyticFunction.setInUse(inUse); } /** * Delegate to the analytic function. * @return Whether it's in use. */ public boolean isInUse() { return myAnalyticFunction.isInUse(); } /** * Returns a copy of this AnalyticAggregator. The internal * AnalyticFunction is replicated, but any * PartitionClause, OrderByClause, or * WindowClause remains the same object(s). * @return A replica of this AnalyticAggregator. */ public AnalyticAggregator replicate() { return new AnalyticAggregator(this); } /** * Returns the PartitionClause, if any. * @return The PartitionClause, if any. */ public PartitionClause getPartition() { return myPartition; } /** * Returns the OrderByClause, if any. * @return The OrderByClause, if any. */ public OrderByClause getOrderBy() { return myOrderBy; } /** * Returns the WindowClause, if any. * @return The WindowClause, if any. */ public WindowClause getWindow() { return myWindow; } /** * Returns a List of AnalyticAggregators on which * this AnalyticAggregator depends, or an empty List * if this doesn't depend on any AnalyticFunctions. * @return A List of AnalyticAggregators. */ public List getDependentAnalyticAggregators() { List dependents = new ArrayList(); if (myAnalyticFunction instanceof DependentAnalyticFunction) { DependentAnalyticFunction depFunc = (DependentAnalyticFunction) myAnalyticFunction; int numFuncs = depFunc.getNumDependentFunctions(); for (int i = 0; i < numFuncs; i++) { // Same partition clause and order by clause, but different // window clauses. AnalyticAggregator depAna = new AnalyticAggregator.Builder() .setAnalyticFunction(depFunc.getAnalyticFunction(i)) .setPartition(getPartition()) .setOrderBy(getOrderBy()) .setWindow(depFunc.getWindowClause(i)) .build(); if (depAna.myAnalyticFunction.getClass().equals(myAnalyticFunction.getClass())) { throw new ParseException("A DependentAnalyticFunction can't depend on itself: " + myAnalyticFunction); } dependents.add(depAna); } } return dependents; } /** * Sets the dependency values in the analytic function, if it's a * DependentAnalyticFunction. * @param anaValue The AnalyticValue containing at least the * values which the analytic function depends, if any. * @param context The AnalyticContext, which knows the analytic * indexes on which this depends on, if any. */ public void setValuesForDependentAnalytics(AnalyticValue anaValue, AnalyticContext context) { if (myAnalyticFunction instanceof DependentAnalyticFunction) { DependentAnalyticFunction depFunc = (DependentAnalyticFunction) myAnalyticFunction; List dependencies = context.getDependencies(); if (dependencies != null) { for (int i = 0; i < dependencies.size(); i++) { // Translate from Analytic index (dependencies.get(i)) // to dependent function index (i). depFunc.setValue(i, anaValue.getAnalyzedValue(dependencies.get(i))); } } } } /** * Determines whether the given AnalyticAggregator is * equivalent to this AnalyticAggregator. This is necessary * because AnalyticAggregator objects will be stored in a * HashMap. * * @param o Another AnalyticAggregator. * @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 AnalyticAggregator. This is * necessary because AnalyticAggregator objects will be stored * in a HashMap. * * @return The hash code of this AnalyticAggregator. It is * computed by taking the hash of the result of the toString * method. * @see #toString */ public int hashCode() { return toString().hashCode(); } /** * Returns the name of the AnalyticFunction class that this * AnalyticAggregator wraps. * @return The name of the AnalyticFunction class that this * AnalyticAggregator wraps. */ public String getAnalyticFunctionClassName() { return myAnalyticFunction.getClass().getName(); } /** * A String representation of this AnalyticAggregator, in the form * "className(property)". * * @return The String representation. */ public String toString() { StringBuilder buf = new StringBuilder(myAnalyticFunction.toString()); if (myPartition != null) { buf.append(" "); buf.append(myPartition.toString()); } if (myOrderBy != null) { buf.append(" "); buf.append(myOrderBy.toString()); } if (myWindow != null) { buf.append(" "); buf.append(myWindow.toString()); } return buf.toString(); } /** * This Builder class follows the "Builder" pattern to create * an AnalyticAggregator object. */ public static class Builder { private AnalyticFunction myAnalyticFunction; private PartitionClause myPartition; private OrderByClause myOrderBy; private WindowClause myWindow; /** * Constructs a Builder with no analytic function, no * partition, no order by clause, and no window clause. */ public Builder() { myAnalyticFunction = null; myPartition = null; myOrderBy = null; myWindow = null; } /** * Sets the AnalyticFunction. Required. * @param func The AnalyticFunction, which must not be * null. * @return This Builder. * @throws IllegalArgumentException If func is * null. */ public Builder setAnalyticFunction(AnalyticFunction func) { if (func == null) throw new IllegalArgumentException("func must not be null!"); myAnalyticFunction = func; return this; } /** * Sets the PartitionClause. Optional. * @param partition The PartitionClause. * @return This Builder. */ public Builder setPartition(PartitionClause partition) { myPartition = partition; return this; } /** * Sets the OrderByClause. Optional. * @param orderBy The OrderByClause. * @return This Builder. */ public Builder setOrderBy(OrderByClause orderBy) { myOrderBy = orderBy; return this; } /** * Sets the WindowClause. Optional. * @param window The WindowClause. * @return This Builder. */ public Builder setWindow(WindowClause window) { myWindow = window; return this; } /** * Build the AnalyticAggregator object. * * @return An AnalyticAggregator. * @throws ParseException If there is a problem with the analytic * specification. * @see #setAnalyticFunction(AnalyticFunction) */ public AnalyticAggregator build() { if (myWindow == null) { // Default window clause. myWindow = WindowClause.DEFAULT; } validate(); if (!myAnalyticFunction.takesWindowClause()) { // If it doesn't take a specified window, then it will supply its // own window. myWindow = myAnalyticFunction.getWindowClause(); } return new AnalyticAggregator(this); } /** * Validates that the given clauses all work well together. Some * AnalyticFunctions may require that the WindowClause * not be specified. * @throws ParseException If there is a problem with the analytic * specification. */ private void validate() { if (myAnalyticFunction == null) { throw new IllegalStateException("Must set an AnalyticFunction!"); } // If window is range with at least one value that is not null or 0, // then the order by clause is required and it is restricted to // exactly one order-by element. Number startValue = myWindow.getStartValue(); Number endValue = myWindow.getEndValue(); if (myWindow.getWindowType() == WindowClause.Type.RANGE && ((startValue != null && startValue.doubleValue() != 0) || (endValue != null && endValue.doubleValue() != 0))) { if (myOrderBy == null || myOrderBy.getElements().size() != 1) throw new ParseException( "Range window with bounds that exist and are not 0 must have exactly one element in the order by clause: " + myAnalyticFunction + " " + myPartition + " " + myOrderBy + " " + myWindow); } // Some analytic functions require there not to be a window clause. if (!myAnalyticFunction.takesWindowClause() && myWindow != WindowClause.DEFAULT) throw new ParseException("Function " + myAnalyticFunction + " does not take a window clause."); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy