blob: 7f13d26f54b1c610594b73f55f7bd2022a9925da [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
package org.eclipse.mdm.businessobjects.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import org.eclipse.mdm.protobuf.Mdm;
import org.eclipse.mdm.protobuf.Mdm.DateArray;
import org.eclipse.mdm.protobuf.Mdm.DoubleArray;
import org.eclipse.mdm.protobuf.Mdm.MeasuredValues;
import org.eclipse.mdm.protobuf.Mdm.MeasuredValues.Builder;
import org.eclipse.mdm.protobuf.Mdm.PreviewValuesList;
import org.eclipse.mdm.protobuf.Mdm.ScalarType;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Ints;
import com.google.protobuf.Timestamp;
/**
* Helper class to calculate preview values.
*
*/
public class PreviewHelper {
/**
* Calcluates the preview values for numerical datatypes. For non numberical
* datatypes an empty MeasuredValues object is returned.
*
* @param measuredValues List of measuredValues containing the mass data for the
* preview.
* @param numberOfChunks number of requested chunks for the preview
* @return calculated preview values
*/
public PreviewValuesList calculatePreview(List<MeasuredValues> measuredValues, int numberOfChunks) {
return measuredValues.stream().map(mv -> calculatePreview(mv, numberOfChunks))
.reduce(Mdm.PreviewValuesList.newBuilder(), (pl1, pl2) -> {
pl1.addAllMin(pl2.getMinList());
pl1.addAllMax(pl2.getMaxList());
pl1.addAllAvg(pl2.getAvgList());
return pl1;
}).build();
}
/**
* Calculate preview for one {@link MeasuredValues}
*
* @param measuredValues measured values
* @param numberOfChunks number of chunks
* @return a builder with the preview values set
*/
protected Mdm.PreviewValuesList.Builder calculatePreview(Mdm.MeasuredValues measuredValues, int numberOfChunks) {
Mdm.MeasuredValues.Builder mv = Mdm.MeasuredValues.newBuilder(measuredValues).setScalarType(ScalarType.DOUBLE);
switch (mv.getValuesCase()) {
case BOOLEAN_ARRAY:
return createEmpty(mv.clearBooleanArray());
case BYTE_ARRAY:
byte[] bytes = measuredValues.getByteArray().getValues().toByteArray();
return calculate(mv, numberOfChunks, Bytes.asList(bytes), measuredValues.getFlagsList());
case BYTE_STREAM_ARRAY:
return createEmpty(mv.clearByteStreamArray());
case DATE_ARRAY:
List<Timestamp> timestamps = measuredValues.getDateArray().getValuesList();
return calculateDate(mv, numberOfChunks, timestamps, measuredValues.getFlagsList());
case DOUBLE_ARRAY:
return calculate(mv, numberOfChunks, measuredValues.getDoubleArray().getValuesList(),
measuredValues.getFlagsList());
case DOUBLE_COMPLEX_ARRAY:
return createEmpty(mv.clearDoubleComplexArray());
case FLOAT_ARRAY:
return calculate(mv, numberOfChunks, measuredValues.getFloatArray().getValuesList(),
measuredValues.getFlagsList());
case FLOAT_COMPLEX_ARRAY:
return createEmpty(mv.clearFloatComplexArray());
case INTEGER_ARRAY:
return calculate(mv, numberOfChunks, measuredValues.getIntegerArray().getValuesList(),
measuredValues.getFlagsList());
case LONG_ARRAY:
return calculate(mv, numberOfChunks, measuredValues.getLongArray().getValuesList(),
measuredValues.getFlagsList());
case SHORT_ARRAY:
return calculate(mv, numberOfChunks, measuredValues.getShortArray().getValuesList(),
measuredValues.getFlagsList());
case STRING_ARRAY:
return createEmpty(mv.clearStringArray());
case VALUES_NOT_SET:
throw new PreviewException(
"Property 'values' of MeasuredValues with name '" + measuredValues.getName() + "' not set.");
default:
throw new PreviewException(
"The value '" + mv.getValuesCase().name() + "' for Property 'values' of MeasuredValues with name '"
+ measuredValues.getName() + "' is not supported.");
}
}
/**
* Returns an empty builder.
*
* @param mv
* @return a builder with no values or flags set
*/
private Mdm.PreviewValuesList.Builder createEmpty(Builder mv) {
return Mdm.PreviewValuesList.newBuilder().addMin(mv.clearFlags().clearLength())
.addMax(mv.clearFlags().clearLength()).addAvg(mv.clearFlags().clearLength());
}
/**
* Calculates min, max and avg for the given values for all data types except
* DT_DATE (@see this{@link #calculateDate(Builder, int, List, List)})
*
* @param builder a builder to apply the values to.
* @param requestedNumberOfChunks number of chunks to calculate
* @param values measurement values
* @param flags list with flags
* @return an builder with the calculated values.
*/
private Mdm.PreviewValuesList.Builder calculate(Mdm.MeasuredValues.Builder builder, int requestedNumberOfChunks,
List<? extends Number> values, List<Boolean> flags) {
if (requestedNumberOfChunks < 1) {
throw new PreviewException("Number of chunks requested must be positive!");
}
// numberOfChunks cannot be larger than length of data
int numberOfChunks = Math.min(requestedNumberOfChunks, values.size());
double chunkSize = values.size() / (double) numberOfChunks;
DoubleArray.Builder minValues = DoubleArray.newBuilder();
DoubleArray.Builder avgValues = DoubleArray.newBuilder();
DoubleArray.Builder maxValues = DoubleArray.newBuilder();
for (int chunkIndex = 0; chunkIndex < numberOfChunks; chunkIndex++) {
double min = Double.MAX_VALUE;
double sum = 0;
double max = Double.MIN_VALUE;
int count = 0;
int startIndex = Ints.checkedCast(Math.round(chunkIndex * chunkSize));
int endIndex = Ints.checkedCast(Math.round((chunkIndex + 1) * chunkSize));
for (int valueIndex = startIndex; valueIndex < endIndex; valueIndex++) {
if (valueIndex >= flags.size() || flags.get(valueIndex)) {
double value = values.get(valueIndex).doubleValue();
sum += value;
min = Math.min(min, value);
max = Math.max(max, value);
count++;
}
}
minValues.addValues(min);
avgValues.addValues(sum / count);
maxValues.addValues(max);
}
builder = builder.setLength(numberOfChunks);
return PreviewValuesList.newBuilder().addMin(builder.setDoubleArray(minValues))
.addAvg(builder.setDoubleArray(avgValues)).addMax(builder.setDoubleArray(maxValues));
}
/**
* Calculates min, max and avg for the given values for all data type DT_DATE.
*
* @param builder a builder to apply the values to.
* @param requestedNumberOfChunks number of chunks to calculate
* @param values measurement values
* @param flags list with flags
* @return an builder with the calculated values.
*/
private Mdm.PreviewValuesList.Builder calculateDate(Mdm.MeasuredValues.Builder builder, int numberOfChunks,
List<Timestamp> timestamps, List<Boolean> flags) {
if (numberOfChunks < 1) {
throw new PreviewException("Number of chunks requested must be positive!");
}
// numberOfChunks cannot be larger than length of data
numberOfChunks = Math.min(numberOfChunks, timestamps.size());
double chunkSize = timestamps.size() / (double) numberOfChunks;
DateArray.Builder minValues = DateArray.newBuilder();
DateArray.Builder avgValues = DateArray.newBuilder();
DateArray.Builder maxValues = DateArray.newBuilder();
for (int chunkIndex = 0; chunkIndex < numberOfChunks; chunkIndex++) {
double sumSeconds = 0.0;
double sumNanos = 0.0;
Timestamp max = Timestamp.newBuilder().setSeconds(0L).setNanos(0).build();
Timestamp min = Timestamp.newBuilder().setSeconds(Long.MAX_VALUE).setNanos(Integer.MAX_VALUE).build();
int count = 0;
int startIndex = Ints.checkedCast(Math.round(chunkIndex * chunkSize));
int endIndex = Ints.checkedCast(Math.round((chunkIndex + 1) * chunkSize));
for (int valueIndex = startIndex; valueIndex < endIndex; valueIndex++) {
if (valueIndex >= flags.size() || flags.get(valueIndex)) {
Timestamp value = timestamps.get(valueIndex);
sumSeconds += value.getSeconds();
sumNanos += value.getNanos();
min = min(min, value);
max = max(max, value);
count++;
}
}
minValues.addValues(min);
maxValues.addValues(max);
BigDecimal c = BigDecimal.valueOf(count);
// calculate average and remainder
BigDecimal[] avg = BigDecimal.valueOf(sumSeconds).add(BigDecimal.valueOf(sumNanos * 1E-9))
.divideAndRemainder(c);
// the remainder will be converted to nanoseconds
avgValues.addValues(Timestamp.newBuilder().setSeconds(avg[0].longValue())
.setNanos(avg[1].multiply(BigDecimal.valueOf(1E9)).divide(c, RoundingMode.HALF_UP).intValue()));
}
return PreviewValuesList.newBuilder().addMin(builder.setDateArray(minValues))
.addAvg(builder.setDateArray(avgValues)).addMax(builder.setDateArray(maxValues));
}
/**
* Returns the smaller of the given timestamps.
*
* @param t1 first timestamp
* @param t2 second timestamp
* @return t1, if t1 before t2 else t2
*/
private Timestamp min(Timestamp t1, Timestamp t2) {
if (t1.getSeconds() < t2.getSeconds()) {
return t1;
} else if (t1.getSeconds() > t2.getSeconds()) {
return t2;
} else {
return t1.getNanos() <= t2.getNanos() ? t1 : t2;
}
}
/**
* Returns the larger of the given timestamps.
*
* @param t1 first timestamp
* @param t2 second timestamp
* @return t1, if t1 after t2 else t2
*/
private Timestamp max(Timestamp t1, Timestamp t2) {
if (t1.getSeconds() > t2.getSeconds()) {
return t1;
} else if (t1.getSeconds() < t2.getSeconds()) {
return t2;
} else {
return t1.getNanos() >= t2.getNanos() ? t1 : t2;
}
}
}