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

org.openjdk.jmc.common.item.Aggregators Maven / Gradle / Ivy

/*
 * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The contents of this file are subject to the terms of either the Universal Permissive License
 * v 1.0 as shown at http://oss.oracle.com/licenses/upl
 *
 * or the following license:
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.openjdk.jmc.common.item;

import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Predicate;

import org.openjdk.jmc.common.messages.internal.Messages;
import org.openjdk.jmc.common.unit.ContentType;
import org.openjdk.jmc.common.unit.IQuantity;
import org.openjdk.jmc.common.unit.IUnit;
import org.openjdk.jmc.common.unit.KindOfQuantity;
import org.openjdk.jmc.common.unit.LinearKindOfQuantity;
import org.openjdk.jmc.common.unit.UnitLookup;
import org.openjdk.jmc.common.util.PredicateToolkit;
import org.openjdk.jmc.common.util.StringToolkit;

public class Aggregators {

	public static abstract class AggregatorBase> implements IAggregator {
		private final String name;
		private final String description;
		private final IType ct;

		public AggregatorBase(String name, String description, IType ct) {
			this.name = name;
			this.description = description;
			this.ct = ct;
		}

		@Override
		public IType getValueType() {
			return ct;
		}

		@Override
		public String getName() {
			return name;
		}

		@Override
		public String getDescription() {
			return description;
		}

	}

	public static abstract class MergingAggregator> extends AggregatorBase {

		public MergingAggregator(String name, String description, IType ct) {
			super(name, description, ct);
		}

		// FIXME: consumers can have different types, they should maybe not be merged?
		// FIXME: Who should be responsible for this merge?
		@Override
		public V getValue(Iterator consumers) {
			if (consumers.hasNext()) {
				C consumer = consumers.next();
				while (consumers.hasNext()) {
					C next = consumers.next();
					consumer = consumer.merge(next);
				}
				return getValue(consumer);
			}
			return null;
		}

		public abstract V getValue(C consumer);

	}

	public static abstract class FieldAggregatorBase> extends MergingAggregator {

		FieldAggregatorBase(String name, String description, IType ct) {
			super(name, description, ct);
		}

		@Override
		public boolean acceptType(IType type) {
			IMemberAccessor a = doGetAccessor(type);
			return a != null;
		}

		protected abstract IMemberAccessor doGetAccessor(IType type);

		IMemberAccessor getAccessor(IType type) {
			IMemberAccessor ma = doGetAccessor(type);
			if (ma != null) {
				return ma;
			}
			throw new IllegalArgumentException("IAggregator.acceptType must be called before newValueConsumer"); //$NON-NLS-1$
		}
	}

	private static abstract class QuantityConsumer implements IItemConsumer {

		IMemberAccessor accessor;

		QuantityConsumer(IMemberAccessor accessor) {
			this.accessor = accessor;
		}
	}

	private static class SumConsumer extends QuantityConsumer {

		double sum = 0.0;
		IUnit unit = null;

		SumConsumer(IMemberAccessor accessor) {
			super(accessor);
		}

		@Override
		public void consume(IItem item) {
			IQuantity fieldValue = accessor.getMember(item);
			if (unit == null) {
				unit = fieldValue.getUnit();
			}
			sum += fieldValue.doubleValueIn(unit);
		}

		@Override
		public SumConsumer merge(SumConsumer other) {
			// FIXME: Should we create a new instance, or is it OK to modify consumers like this?
			if (unit != null) {
				if (other.unit != null) {
					sum += other.unit.valueTransformTo(unit).targetValue(other.sum);
				}
				return this;
			}
			return other;
		}

	}

	public static abstract class Sum extends FieldAggregatorBase {

		public Sum(String name, String description, LinearKindOfQuantity ct) {
			super(name, description, ct);
		}

		@Override
		public SumConsumer newItemConsumer(IType type) {
			return new SumConsumer(getAccessor(type));
		}

		@Override
		public IQuantity getValue(SumConsumer consumer) {
			return consumer.unit != null ? consumer.unit.quantity(consumer.sum) : null;
		}

	}

	public static abstract class Variance extends FieldAggregatorBase {
		private final boolean besselCorrection;

		public Variance(String name, String description, LinearKindOfQuantity ct, boolean besselCorrection) {
			super(name, description, ct);
			this.besselCorrection = besselCorrection;
		}

		@Override
		public VarianceConsumer newItemConsumer(IType type) {
			return new VarianceConsumer(getAccessor(type));
		}

		@Override
		public IQuantity getValue(VarianceConsumer consumer) {
			if (consumer.unit == null) {
				return null;
			}
			Number variance = consumer.getVariance(besselCorrection);
			return variance == null ? null : consumer.unit.quantity(variance);
		}
	}

	public static abstract class Stddev extends FieldAggregatorBase {
		private final boolean besselCorrection;

		public Stddev(String name, String description, LinearKindOfQuantity ct, boolean besselCorrection) {
			super(name, description, ct);
			this.besselCorrection = besselCorrection;
		}

		@Override
		public VarianceConsumer newItemConsumer(IType type) {
			return new VarianceConsumer(getAccessor(type));
		}

		@Override
		public IQuantity getValue(VarianceConsumer consumer) {
			if (consumer.unit == null) {
				return null;
			}
			Number stddev = consumer.getStddev(besselCorrection);
			return stddev == null ? null : consumer.unit.quantity(stddev);
		}
	}

	/**
	 * Consumer for calculating stddev and variance in a one pass, numerically stable way.
	 */
	public static class VarianceConsumer extends QuantityConsumer {
		public long n = 0;
		public double mean = 0.0;
		public double M2 = 0.0;
		public IUnit unit = null;

		public VarianceConsumer(IMemberAccessor accessor) {
			super(accessor);
		}

		public Number getVariance(boolean besselCorrection) {
			long divisor = besselCorrection ? n - 1 : n;
			if (divisor < 0) {
				return null;
			}
			if (divisor == 0) {
				return besselCorrection ? null : 0;
			}
			return M2 / divisor;
		}

		public Number getStddev(boolean besselCorrection) {
			Number variance = getVariance(besselCorrection);
			if (variance == null) {
				return null;
			}
			return Math.sqrt(variance.doubleValue());
		}

		@Override
		public void consume(IItem item) {
			IQuantity fieldValue = accessor.getMember(item);
			n++;
			if (fieldValue == null) {
				return;
			}
			if (unit == null) {
				unit = fieldValue.getUnit();
			}
			double x = fieldValue.doubleValueIn(unit);
			double delta = x - mean;
			mean = mean + delta / n;
			M2 = M2 + delta * (x - mean);
		}

		@Override
		public VarianceConsumer merge(VarianceConsumer other) {
			if (unit == null) {
				unit = other.unit;
			}
			if (unit != null) {
				if (other.unit != null) {
					/*
					 * Since the Knuth algorithm (Chan et. al) can be used with partitioned sets, we
					 * can combine the results from two aggregators with partial results like below.
					 */
					double otherMean = other.unit.valueTransformTo(unit).targetValue(other.mean);
					double otherM2 = other.unit.valueTransformTo(unit).targetValue(other.M2);
					double deltaMean = otherMean - mean;
					mean = mean + deltaMean * (other.n / (double) (other.n + n));
					M2 = otherM2 + M2 + deltaMean * deltaMean * (other.n * n / (double) (n + other.n));
					n += other.n;
				}
				return this;
			}
			return other;
		}
	}

	// FIXME: Would like to extend SumConsumer, but this currently causes generics problems
	public static class AvgConsumer extends QuantityConsumer {

		public double sum = 0.0;
		public IUnit unit = null;
		public int count = 0;

		public AvgConsumer(IMemberAccessor accessor) {
			super(accessor);
		}

		@Override
		public void consume(IItem item) {
			IQuantity fieldValue = accessor.getMember(item);
			if (fieldValue == null) {
				count++;
				return;
			}
			if (unit == null) {
				unit = fieldValue.getUnit();
			}
			sum += fieldValue.doubleValueIn(unit);
			count++;
		}

		@Override
		public AvgConsumer merge(AvgConsumer other) {
			if (unit == null) {
				unit = other.unit;
			}
			if (unit != null) {
				if (other.unit != null) {
					sum += other.unit.valueTransformTo(unit).targetValue(other.sum);
					count += other.count;
				}
				return this;
			}
			return other;
		}

	}

	public static abstract class Avg extends FieldAggregatorBase {

		public Avg(String name, String description, ContentType ct) {
			super(name, description, ct);
		}

		@Override
		public AvgConsumer newItemConsumer(IType type) {
			return new AvgConsumer(getAccessor(type));
		}

		@Override
		public IQuantity getValue(AvgConsumer consumer) {
			return consumer.unit == null ? null : consumer.unit.quantity(consumer.sum / consumer.count);
		}
	}

	public static class MinMaxConsumer> implements IItemConsumer> {

		private final IMemberAccessor accessor;
		private final boolean max;
		private V value;
		private IItem item;

		public MinMaxConsumer(IMemberAccessor accessor, boolean max) {
			this.accessor = accessor;
			this.max = max;
		}

		@Override
		public void consume(IItem item) {
			add(accessor.getMember(item), item);
		}

		// FIXME: "add" is not an ideal name for this method, rename to something better
		private void add(V newValue, IItem newItem) {
			if (newValue != null && (value == null || newValue.compareTo(value) > 0 == max)) {
				value = newValue;
				item = newItem;
			}
		}

		@Override
		public MinMaxConsumer merge(MinMaxConsumer other) {
			add(other.value, other.item);
			return this;
		}
	}

	public static abstract class MinMax> extends FieldAggregatorBase> {
		private final boolean max;

		MinMax(String name, String description, ContentType ct, boolean max) {
			super(name, description, ct);
			this.max = max;
		}

		@Override
		public MinMaxConsumer newItemConsumer(IType type) {
			return new MinMaxConsumer<>(getAccessor(type), max);
		}

		@Override
		public V getValue(MinMaxConsumer consumer) {
			return consumer.value;
		}
	}

	public static class CountConsumer implements IItemConsumer {

		private int count = 0;

		@Override
		public void consume(IItem item) {
			count++;
		}

		@Override
		public CountConsumer merge(CountConsumer other) {
			count += other.count;
			return this;
		}

		public int getCount() {
			return count;
		}

	}

	private static class FilterConsumer> implements IItemConsumer> {

		private final Predicate p;
		private final C nestedConsumer;

		public FilterConsumer(Predicate p, C nestedConsumer) {
			this.p = p;
			this.nestedConsumer = nestedConsumer;
		}

		@Override
		public void consume(IItem item) {
			if (p.test(item)) {
				nestedConsumer.consume(item);
			}
		}

		@Override
		public FilterConsumer merge(FilterConsumer other) {
			nestedConsumer.merge(other.nestedConsumer);
			return this;
		}
	}

	private static class Count extends MergingAggregator {

		Count(String name, String description) {
			super(name, description, UnitLookup.NUMBER);
		}

		@Override
		public boolean acceptType(IType type) {
			return true;
		}

		@Override
		public CountConsumer newItemConsumer(IType type) {
			return new CountConsumer();
		}

		@Override
		public IQuantity getValue(CountConsumer consumer) {
			return UnitLookup.NUMBER_UNITY.quantity(consumer.count);
		}
	}

	private static final Count COUNT = new Count(Messages.getString(Messages.ItemAggregate_COUNT), null);

	private static class AndOrConsumer implements IItemConsumer {
		boolean and;
		Boolean b;
		IMemberAccessor accessor;

		public AndOrConsumer(IMemberAccessor accessor, boolean and) {
			this.and = and;
			this.accessor = accessor;
		}

		@Override
		public void consume(IItem item) {
			evaluate(accessor.getMember(item));
		}

		void evaluate(Boolean value) {
			if (b == null) {
				b = value;
			} else if (value != null) {
				b = and ? b && value : b || value;
			}
		}

		@Override
		public AndOrConsumer merge(AndOrConsumer other) {
			evaluate(other.b);
			return this;
		}

	}

	private static abstract class AndOr extends FieldAggregatorBase {

		public AndOr(String name, String description, IType ct) {
			super(name, description, ct);
		}

		@Override
		public Boolean getValue(AndOrConsumer consumer) {
			return consumer.b;
		}

	}

	private static > IAggregator minMaxItem(
		String name, final IAttribute attribute, boolean max) {
		return new MergingAggregator>("Item with " + name, null, UnitLookup.UNKNOWN) { //$NON-NLS-1$

			@Override
			public boolean acceptType(IType type) {
				return attribute.getAccessor(type) != null;
			}

			@Override
			public MinMaxConsumer newItemConsumer(IType type) {
				return new MinMaxConsumer<>(attribute.getAccessor(type), true);
			}

			@Override
			public IItem getValue(MinMaxConsumer consumer) {
				return consumer.item;
			}

		};
	}

	public static > IAggregator itemWithMin(IAttribute attribute) {
		String name = getMinName(attribute.getName(), attribute.getContentType());
		return minMaxItem(name, attribute, false);
	}

	public static > IAggregator itemWithMax(IAttribute attribute) {
		String name = getMaxName(attribute.getName(), attribute.getContentType());
		return minMaxItem(name, attribute, true);
	}

	public static  IAggregator filter(IAggregator aggregator, IItemFilter filter) {
		return filter(aggregator.getName(), aggregator.getDescription(), aggregator, filter);
	}

	public static > IAggregator filter(
		String name, String description, final IAggregator aggregator, final IItemFilter filter) {
		return new AggregatorBase>(name, description, aggregator.getValueType()) {

			@Override
			public boolean acceptType(IType type) {
				return aggregator.acceptType(type) && !PredicateToolkit.isFalseGuaranteed(filter.getPredicate(type));
			}

			@Override
			public FilterConsumer newItemConsumer(IType type) {
				return new FilterConsumer<>(filter.getPredicate(type), aggregator.newItemConsumer(type));
			}

			@Override
			public V getValue(final Iterator> consumers) {
				return aggregator.getValue(new Iterator() {

					@Override
					public boolean hasNext() {
						return consumers.hasNext();
					}

					@Override
					public C next() {
						return consumers.next().nestedConsumer;
					}

					@Override
					public void remove() {
						throw new UnsupportedOperationException();
					}
				});
			}
		};
	}

	public static IAggregator sum(final IAttribute attribute) {
		ContentType contentType = attribute.getContentType();
		if (contentType instanceof LinearKindOfQuantity) {
			return new Sum(getSumName(attribute.getName()), attribute.getDescription(),
					(LinearKindOfQuantity) contentType) {

				@Override
				protected IMemberAccessor doGetAccessor(IType type) {
					return attribute.getAccessor(type);
				}

			};
		}
		throw new IllegalArgumentException("Can only use LinearKindOfQuantity"); //$NON-NLS-1$
	}

	/**
	 * Calculates the sample variance for a linear quantity attribute.
	 *
	 * @param attribute
	 *            the attribute to calculate the sample variance for
	 * @return the variance for the attribute
	 */
	public static IAggregator variance(final IAttribute attribute) {
		return varianceInternal(attribute, true);
	}

	/**
	 * Calculates the population variance for a linear quantity attribute.
	 *
	 * @param attribute
	 *            the attribute to calculate the population variance for
	 * @return the variance for the attribute
	 */
	public static IAggregator variancep(final IAttribute attribute) {
		return varianceInternal(attribute, false);
	}

	private static IAggregator varianceInternal(
		final IAttribute attribute, boolean besselCorrection) {
		ContentType contentType = attribute.getContentType();
		if (contentType instanceof LinearKindOfQuantity) {
			return new Variance(getVarianceName(attribute.getName(), besselCorrection), attribute.getDescription(),
					(LinearKindOfQuantity) contentType, besselCorrection) {
				@Override
				protected IMemberAccessor doGetAccessor(IType type) {
					return attribute.getAccessor(type);
				}
			};
		}
		throw new IllegalArgumentException("Can only use LinearKindOfQuantity"); //$NON-NLS-1$
	}

	/**
	 * Calculates the sample standard deviation for a linear quantity attribute.
	 *
	 * @param attribute
	 *            the attribute to calculate the sample standard deviation for
	 * @return the standard deviation for the attribute
	 */
	public static IAggregator stddev(final IAttribute attribute) {
		return stddevInternal(attribute, true);
	}

	/**
	 * Calculates the sample standard deviation for a linear quantity attribute.
	 *
	 * @param name
	 *            aggregator name
	 * @param description
	 *            aggregator description
	 * @param attribute
	 *            the attribute to calculate the sample standard deviation for
	 * @return an aggregator that calculates the standard deviation for the attribute
	 */
	public static IAggregator stddev(
		String name, String description, final IAttribute attribute) {
		return stddevInternal(name, description, attribute, true);
	}

	/**
	 * Calculates the population standard deviation for a linear quantity attribute.
	 *
	 * @param attribute
	 *            the attribute to calculate the population standard deviation for
	 * @return an aggregator that calculates the standard deviation for the attribute
	 */
	public static IAggregator stddevp(final IAttribute attribute) {
		return stddevInternal(attribute, false);
	}

	/**
	 * Calculates the population standard deviation for a linear quantity attribute.
	 *
	 * @param name
	 *            aggregator name
	 * @param description
	 *            aggregator description
	 * @param attribute
	 *            the attribute to calculate the population standard deviation for
	 * @return an aggregator that calculates the standard deviation for the attribute
	 */
	public static IAggregator stddevp(
		String name, String description, final IAttribute attribute) {
		return stddevInternal(name, description, attribute, false);
	}

	private static IAggregator stddevInternal(
		final IAttribute attribute, boolean besselCorrection) {
		return stddevInternal(getStddevName(attribute.getName(), besselCorrection), attribute.getDescription(),
				attribute, besselCorrection);
	}

	private static IAggregator stddevInternal(
		String name, String description, final IAttribute attribute, boolean besselCorrection) {
		ContentType contentType = attribute.getContentType();
		if (contentType instanceof LinearKindOfQuantity) {
			return new Stddev(name, description, (LinearKindOfQuantity) contentType, besselCorrection) {
				@Override
				protected IMemberAccessor doGetAccessor(IType type) {
					return attribute.getAccessor(type);
				}
			};
		}
		throw new IllegalArgumentException("Can only use LinearKindOfQuantity"); //$NON-NLS-1$
	}

	public static IAggregator sum(final String typeId, final IAttribute attribute) {
		return sum(getSumName(attribute.getName()), null, typeId, attribute);
	}

	public static IAggregator sum(String name, String description, IAttribute attribute) {
		ContentType contentType = attribute.getContentType();
		if (contentType instanceof LinearKindOfQuantity) {
			return sum(name, description, (LinearKindOfQuantity) contentType, attribute);
		}
		throw new IllegalArgumentException("Can only use LinearKindOfQuantity"); //$NON-NLS-1$
	}

	public static IAggregator sum(
		String name, String description, final String typeId, final IAttribute attribute) {
		ContentType contentType = attribute.getContentType();
		if (contentType instanceof LinearKindOfQuantity) {
			return new Sum(name, description, (LinearKindOfQuantity) contentType) {

				@Override
				protected IMemberAccessor doGetAccessor(IType type) {
					if (type.getIdentifier().equals(typeId)) {
						return attribute.getAccessor(type);
					}
					return null;
				}

			};
		}
		throw new IllegalArgumentException("Can only use LinearKindOfQuantity"); //$NON-NLS-1$
	}

	public static IAggregator sum(
		String name, String description, LinearKindOfQuantity ct, final IAccessorFactory af) {
		return new Sum(name, description, ct) {

			@Override
			protected IMemberAccessor doGetAccessor(IType type) {
				return af.getAccessor(type);
			}

		};
	}

	public static IAggregator avg(final IAttribute attribute) {
		return new Avg(getAvgName(attribute.getName()), attribute.getDescription(), attribute.getContentType()) {

			@Override
			protected IMemberAccessor doGetAccessor(IType type) {
				return attribute.getAccessor(type);
			}

		};
	}

	public static IAggregator avg(final String typeId, final IAttribute attribute) {
		return avg(getAvgName(attribute.getName()), null, typeId, attribute);
	}

	public static IAggregator avg(String name, String description, IAttribute attribute) {
		ContentType contentType = attribute.getContentType();
		if (contentType instanceof KindOfQuantity) {
			return avg(name, description, (KindOfQuantity) contentType, attribute);
		}
		throw new IllegalArgumentException("Can only use KindOfQuantity"); //$NON-NLS-1$
	}

	public static IAggregator avg(
		String name, String description, final String typeId, final IAttribute attribute) {
		ContentType contentType = attribute.getContentType();
		if (contentType instanceof KindOfQuantity) {
			return new Avg(name, description, (KindOfQuantity) contentType) {

				@Override
				protected IMemberAccessor doGetAccessor(IType type) {
					if (type.getIdentifier().equals(typeId)) {
						return attribute.getAccessor(type);
					}
					return null;
				}

			};
		}
		throw new IllegalArgumentException("Can only use KindOfQuantity"); //$NON-NLS-1$
	}

	public static IAggregator avg(
		String name, String description, KindOfQuantity ct, final IAccessorFactory af) {
		return new Avg(name, description, ct) {

			@Override
			protected IMemberAccessor doGetAccessor(IType type) {
				return af.getAccessor(type);
			}

		};
	}

	public static > IAggregator min(final IAttribute attribute) {
		String name = getMinName(attribute.getName(), attribute.getContentType());
		return new MinMax(name, attribute.getDescription(), attribute.getContentType(), false) {

			@Override
			protected IMemberAccessor doGetAccessor(IType type) {
				return attribute.getAccessor(type);
			}

		};
	}

	public static > IAggregator min(final String typeId, final IAttribute attribute) {
		String name = getMinName(attribute.getName(), attribute.getContentType());
		return min(name, null, typeId, attribute);
	}

	public static > IAggregator min(
		String name, String description, final String typeId, final IAttribute attribute) {
		ContentType contentType = attribute.getContentType();
		return new MinMax(name, description, contentType, false) {

			@Override
			protected IMemberAccessor doGetAccessor(IType type) {
				if (type.getIdentifier().equals(typeId)) {
					return attribute.getAccessor(type);
				}
				return null;
			}

		};
	}

	public static > IAggregator max(final IAttribute attribute) {
		String name = getMaxName(attribute.getName(), attribute.getContentType());
		return new MinMax(name, attribute.getDescription(), attribute.getContentType(), true) {

			@Override
			protected IMemberAccessor doGetAccessor(IType type) {
				return attribute.getAccessor(type);
			}

		};
	}

	public static IAggregator max(final String typeId, final IAttribute attribute) {
		String name = getMaxName(attribute.getName(), attribute.getContentType());
		return max(name, null, typeId, attribute);
	}

	public static > IAggregator max(
		String name, String description, final IAttribute attribute) {
		ContentType contentType = attribute.getContentType();
		return new MinMax(name, description, contentType, true) {

			@Override
			protected IMemberAccessor doGetAccessor(IType type) {
				return attribute.getAccessor(type);
			}
		};
	}

	public static > IAggregator max(
		String name, String description, final String typeId, final IAttribute attribute) {
		ContentType contentType = attribute.getContentType();
		return new MinMax(name, description, contentType, true) {

			@Override
			protected IMemberAccessor doGetAccessor(IType type) {
				if (type.getIdentifier().equals(typeId)) {
					return attribute.getAccessor(type);
				}
				return null;
			}
		};
	}

	public static IAggregator count() {
		return COUNT;
	}

	public static IAggregator count(String name, String description) {
		return new Count(name, description);
	}

	public static IAggregator count(IType type) {
		return filter(getCountName(type), null, COUNT, ItemFilters.type(type.getIdentifier()));
	}

	public static IAggregator count(IItemFilter filter) {
		return filter(COUNT, filter);
	}

	public static IAggregator count(String name, String description, IItemFilter filter) {
		return filter(name, description, COUNT, filter);
	}

	public static IAggregator and(final String typeId, final IAttribute attribute) {
		return new AndOr(attribute.getName(), attribute.getDescription(), UnitLookup.FLAG) {

			@Override
			public AndOrConsumer newItemConsumer(IType type) {
				return new AndOrConsumer(attribute.getAccessor(type), true);
			}

			@Override
			public boolean acceptType(IType type) {
				return type.getIdentifier().equals(typeId);
			}

			@Override
			protected IMemberAccessor doGetAccessor(IType type) {
				if (type.getIdentifier().equals(typeId)) {
					return attribute.getAccessor(type);
				}
				return null;
			}
		};
	}

	public static IAggregator or(final String typeId, final IAttribute attribute) {
		return new AndOr(attribute.getName(), attribute.getDescription(), UnitLookup.FLAG) {

			@Override
			public AndOrConsumer newItemConsumer(IType type) {
				return new AndOrConsumer(attribute.getAccessor(type), false);
			}

			@Override
			public boolean acceptType(IType type) {
				return type.getIdentifier().equals(typeId);
			}

			@Override
			protected IMemberAccessor doGetAccessor(IType type) {
				if (type.getIdentifier().equals(typeId)) {
					return attribute.getAccessor(type);
				}
				return null;
			}
		};
	}

	public static class SetConsumer implements IItemConsumer> {
		Set distinct = new HashSet<>();
		private final IMemberAccessor accessor;

		public SetConsumer(IMemberAccessor accessor) {
			this.accessor = accessor;
		}

		@Override
		public void consume(IItem item) {
			T member = accessor.getMember(item);
			if (member != null) {
				distinct.add(member);
			}
		}

		@Override
		public SetConsumer merge(SetConsumer other) {
			distinct.addAll(other.distinct);
			return this;
		}
	}

	private abstract static class SetAggregator extends MergingAggregator> {

		private final IAccessorFactory attribute;

		public SetAggregator(String name, String description, IAccessorFactory attribute, IType type) {
			super(name, description, type);
			this.attribute = attribute;
		}

		@Override
		public boolean acceptType(IType type) {
			return attribute.getAccessor(type) != null;
		}

		@Override
		public SetConsumer newItemConsumer(IType itemType) {
			return new SetConsumer<>(attribute.getAccessor(itemType));
		}
	};

	public static IAggregator distinctAsString(String typeId, IAttribute attribute) {
		return filter(distinctAsString(attribute, ", "), ItemFilters.type(typeId)); //$NON-NLS-1$
	}

	public static IAggregator distinctAsString(IAttribute attribute, final String delimiter) {
		return distinctAsString(attribute, delimiter, attribute.getName(), attribute.getDescription());
	}

	public static IAggregator distinctAsString(
		IAttribute attribute, final String delimiter, String name, String description) {
		IAggregator, ?> aggregator = Aggregators.distinct(attribute);
		return Aggregators.valueBuilderAggregator(aggregator, new IValueBuilder>() {

			@Override
			public String getValue(Set source) {
				return source.isEmpty() ? null : StringToolkit.join(source, delimiter);
			}

			@Override
			public IType getValueType() {
				return UnitLookup.PLAIN_TEXT;
			}
		}, name, description);
	}

	public static > IAggregator valueBuilderAggregator(
		final IAggregator aggregator, final IValueBuilder valuebuilder, String name,
		String description) {

		return new AggregatorBase(name, description, valuebuilder.getValueType()) {

			@Override
			public boolean acceptType(IType type) {
				return aggregator.acceptType(type);
			}

			@Override
			public C newItemConsumer(IType type) {
				return aggregator.newItemConsumer(type);
			}

			@Override
			public V2 getValue(final Iterator consumers) {
				V1 val1 = aggregator.getValue(consumers);
				return val1 != null ? valuebuilder.getValue(val1) : null;
			}
		};
	}

	public static  IAggregator countDistinct(
		String name, String description, IAccessorFactory attribute) {
		return new SetAggregator(name, description, attribute, UnitLookup.NUMBER) {

			@Override
			public IQuantity getValue(SetConsumer consumer) {
				return UnitLookup.NUMBER_UNITY.quantity(consumer.distinct.size());
			}

		};
	}

	public static  IAggregator, ?> distinct(IAttribute attribute) {
		return distinct(MessageFormat.format(Messages.getString(Messages.ItemAggregate_DISTINCT), attribute.getName()),
				attribute);
	}

	public static  IAggregator, ?> distinct(String name, IAccessorFactory attribute) {
		return new SetAggregator, T>(name, null, attribute, UnitLookup.UNKNOWN) {

			@Override
			public Set getValue(SetConsumer consumer) {
				return consumer.distinct;
			}

		};
	}

	public static > IAggregator forConsumer(IItemConsumerFactory consumerFactory) {
		return forConsumer(consumerFactory, PredicateToolkit.> truePredicate());
	}

	public static > IAggregator forConsumer(
		final IItemConsumerFactory consumerFactory, final Predicate> acceptType) {
		return new MergingAggregator("", null, UnitLookup.UNKNOWN) { //$NON-NLS-1$

			@Override
			public boolean acceptType(IType type) {
				return acceptType.test(type);
			}

			@Override
			public C newItemConsumer(IType type) {
				return consumerFactory.newItemConsumer(type);
			}

			@Override
			public C getValue(C consumer) {
				return consumer;
			}

		};
	}

	private static String getCountName(IType type) {
		return Messages.getString(Messages.ItemAggregate_COUNT) + " " + type.getName(); //$NON-NLS-1$
	}

	static String getSumName(String name) {
		return Messages.getString(Messages.ItemAggregate_TOTAL) + " " + name; //$NON-NLS-1$
	}

	static String getVarianceName(String name, boolean besselCorrection) {
		return Messages.getString(besselCorrection ? Messages.ItemAggregate_VARIANCE : Messages.ItemAggregate_VARIANCEP)
				+ " " + name; //$NON-NLS-1$
	}

	static String getStddevName(String name, boolean besselCorrection) {
		return Messages.getString(besselCorrection ? Messages.ItemAggregate_STDDEV : Messages.ItemAggregate_STDDEVP)
				+ " " + name; //$NON-NLS-1$
	}

	static String getAvgName(String name) {
		return Messages.getString(Messages.ItemAggregate_AVERAGE) + " " + name; //$NON-NLS-1$
	}

	static String getMaxName(String name, ContentType ct) {
		if (ct == UnitLookup.TIMESPAN) {
			return Messages.getString(Messages.ItemAggregate_LONGEST) + " " + name; //$NON-NLS-1$
		} else if (ct == UnitLookup.TIMESTAMP) {
			return Messages.getString(Messages.ItemAggregate_LAST) + " " + name; //$NON-NLS-1$
		} else {
			return Messages.getString(Messages.ItemAggregate_MAXIMUM) + " " + name; //$NON-NLS-1$
		}
	}

	static String getMinName(String name, ContentType ct) {
		if (ct == UnitLookup.TIMESPAN) {
			return Messages.getString(Messages.ItemAggregate_SHORTEST) + " " + name; //$NON-NLS-1$
		} else if (ct == UnitLookup.TIMESTAMP) {
			return Messages.getString(Messages.ItemAggregate_FIRST) + " " + name; //$NON-NLS-1$
		} else {
			return Messages.getString(Messages.ItemAggregate_MINIMUM) + " " + name; //$NON-NLS-1$
		}
	}

	// FIXME: Translated strings are not good for persistence purposes. Maybe do something else.
	public static IAggregator getQuantityAggregator(String name, IAttribute attribute) {
		if (name == null) {
			return null;
		}
		if (name.startsWith(Messages.getString(Messages.ItemAggregate_TOTAL))) {
			return sum(attribute);
		} else if (name.startsWith(Messages.getString(Messages.ItemAggregate_AVERAGE))) {
			return avg(attribute);
		} else if (name.startsWith(Messages.getString(Messages.ItemAggregate_LONGEST))) {
			return max(attribute);
		} else if (name.startsWith(Messages.getString(Messages.ItemAggregate_LAST))) {
			return max(attribute);
		} else if (name.startsWith(Messages.getString(Messages.ItemAggregate_MAXIMUM))) {
			return max(attribute);
		} else if (name.startsWith(Messages.getString(Messages.ItemAggregate_SHORTEST))) {
			return min(attribute);
		} else if (name.startsWith(Messages.getString(Messages.ItemAggregate_FIRST))) {
			return min(attribute);
		} else if (name.startsWith(Messages.getString(Messages.ItemAggregate_MINIMUM))) {
			return min(attribute);
		} else {
			return null;
		}
	}

	public static IAggregator getQuantityAggregator(String name, IType type) {
		if (type != null) {
			if (name.startsWith(Messages.getString(Messages.ItemAggregate_COUNT))) {
				return count(type);
			}
		}
		if (name.startsWith(Messages.getString(Messages.ItemAggregate_COUNT))) {
			return count();
		}
		return null;
	}

	public static IAggregator getQuantityAggregator(String name) {
		if (name.startsWith(Messages.getString(Messages.ItemAggregate_COUNT))) {
			return count();
		}
		return null;
	}

	/**
	 * This consumer separates the attribute for which to do the ordering from the attribute to use
	 * for accessing the value. It is typically used within the AdvancedMinMaxAggregator for getting
	 * a specific value from the first or last event from a collection of events.
	 *
	 * @param 
	 *            the return value type, for example {@code java.lang.String}
	 * @param 
	 *            the value type for the ordering
	 */
	public static class AdvancedMinMaxConsumer>
			implements IItemConsumer> {
		private final IMemberAccessor accessor;
		private final IMemberAccessor comparatorAccessor;
		private final boolean max;
		private IItem item;

		/**
		 * @param valueAccessor
		 *            the accessor for retrieving the value
		 * @param comparatorAccessor
		 *            the accessor for retrieving the value to use for comparisons
		 * @param max
		 *            whether to use the smallest value, or the greatest
		 */
		public AdvancedMinMaxConsumer(IMemberAccessor valueAccessor,
				IMemberAccessor comparatorAccessor, boolean max) {
			this.accessor = valueAccessor;
			this.max = max;
			this.comparatorAccessor = comparatorAccessor;
		}

		@Override
		public void consume(IItem newItem) {
			if (newItem == null) {
				return;
			}
			T newOrderingValue = comparatorAccessor.getMember(newItem);
			if (newOrderingValue == null) {
				return;
			} else if (item == null) {
				item = newItem;
			} else {
				T oldOrderingValue = comparatorAccessor.getMember(item);
				if (newOrderingValue.compareTo(oldOrderingValue) > 0 == max) {
					item = newItem;
				}
			}
		}

		@Override
		public AdvancedMinMaxConsumer merge(AdvancedMinMaxConsumer other) {
			consume(other.item);
			return this;
		}

		public V getValue() {
			if (item == null) {
				return null;
			}
			return accessor.getMember(item);
		}
	}

	/**
	 * This aggregator separates the attribute for which to do the ordering from the attribute to
	 * use for accessing the value. It is typically used for getting a specific value from the first
	 * or last event from a collection of events.
	 *
	 * @param 
	 *            the return value type, for example {@code java.lang.String}
	 * @param 
	 *            the value type for the ordering
	 */
	private static class AdvancedMinMaxAggregator>
			extends FieldAggregatorBase> {
		private final boolean max;
		private final IAttribute attribute;
		private final IAttribute comparator;

		public AdvancedMinMaxAggregator(String name, String description, IAttribute attribute,
				IAttribute comparator, boolean max) {
			super(name, description, attribute.getContentType());
			this.attribute = attribute;
			this.comparator = comparator;
			this.max = max;
		}

		@Override
		public AdvancedMinMaxConsumer newItemConsumer(IType type) {
			return new AdvancedMinMaxConsumer<>(attribute.getAccessor(type), comparator.getAccessor(type), max);
		}

		@Override
		public V getValue(AdvancedMinMaxConsumer consumer) {
			return consumer.getValue();
		}

		@Override
		protected IMemberAccessor doGetAccessor(IType type) {
			return attribute.getAccessor(type);
		}
	}

	/**
	 * This aggregator separates the attribute for which to do the ordering from the attribute to
	 * use for accessing the value. It is typically used for getting a specific value from the first
	 * event from a collection of events.
	 * 

* For example:
* AdvancedMinAggregator<String, IQuantity> aggregator = new * AdvancedMinAggregator(myFavouriteAttribute, startTimeAttribute); * * @param * the return value type, for example {@code java.lang.String} * @param * the value type for the ordering */ public static class AdvancedMinAggregator> extends AdvancedMinMaxAggregator { public AdvancedMinAggregator(String name, String description, IAttribute attribute, IAttribute comparator) { super(name, description, attribute, comparator, false); } } /** * This aggregator separates the attribute for which to do the ordering from the attribute to * use for accessing the value. It is typically used for getting a specific value from the last * event from a collection of events. *

* For example:
* AdvancedMaxAggregator<String, IQuantity> aggregator = new * AdvancedMaxAggregator(myFavouriteAttribute, endTimeAttribute); * * @param * the return value type, for example {@code java.lang.String} * @param * the value type for the ordering */ public static class AdvancedMaxAggregator> extends AdvancedMinMaxAggregator { public AdvancedMaxAggregator(String name, String description, IAttribute attribute, IAttribute comparator) { super(name, description, attribute, comparator, true); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy