/******************************************************************************** | |
* 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.io.ByteArrayOutputStream; | |
import java.time.Instant; | |
import java.time.LocalDateTime; | |
import java.time.ZoneId; | |
import java.time.ZoneOffset; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Optional; | |
import java.util.stream.Collectors; | |
import org.eclipse.mdm.api.base.ServiceNotProvidedException; | |
import org.eclipse.mdm.api.base.massdata.ReadRequest; | |
import org.eclipse.mdm.api.base.massdata.ReadRequest.ValuesMode; | |
import org.eclipse.mdm.api.base.massdata.ReadRequestBuilder; | |
import org.eclipse.mdm.api.base.model.AxisType; | |
import org.eclipse.mdm.api.base.model.Channel; | |
import org.eclipse.mdm.api.base.model.ChannelGroup; | |
import org.eclipse.mdm.api.base.model.DoubleComplex; | |
import org.eclipse.mdm.api.base.model.FloatComplex; | |
import org.eclipse.mdm.api.base.model.MeasuredValues; | |
import org.eclipse.mdm.api.base.model.MeasuredValues.ValueIterator; | |
import org.eclipse.mdm.api.base.model.ScalarType; | |
import org.eclipse.mdm.api.base.model.Unit; | |
import org.eclipse.mdm.api.dflt.ApplicationContext; | |
import org.eclipse.mdm.api.dflt.EntityManager; | |
import org.eclipse.mdm.protobuf.Mdm; | |
import org.eclipse.mdm.protobuf.Mdm.BooleanArray; | |
import org.eclipse.mdm.protobuf.Mdm.ByteArray; | |
import org.eclipse.mdm.protobuf.Mdm.ByteStreamArray; | |
import org.eclipse.mdm.protobuf.Mdm.DateArray; | |
import org.eclipse.mdm.protobuf.Mdm.DoubleArray; | |
import org.eclipse.mdm.protobuf.Mdm.DoubleComplexArray; | |
import org.eclipse.mdm.protobuf.Mdm.FloatArray; | |
import org.eclipse.mdm.protobuf.Mdm.FloatComplexArray; | |
import org.eclipse.mdm.protobuf.Mdm.IntegerArray; | |
import org.eclipse.mdm.protobuf.Mdm.LongArray; | |
import org.eclipse.mdm.protobuf.Mdm.MeasuredValuesList; | |
import org.eclipse.mdm.protobuf.Mdm.ShortArray; | |
import org.eclipse.mdm.protobuf.Mdm.StringArray; | |
import com.google.common.base.Strings; | |
import com.google.common.primitives.Doubles; | |
import com.google.protobuf.ByteString; | |
import com.google.protobuf.Timestamp; | |
/** | |
* Helper class for converting between protobuf and mdm types. | |
* | |
*/ | |
public class ProtobufConverter { | |
/** | |
* Converted a DateArray to an array of {@link LocalDateTime} | |
* | |
* @param dateArray | |
* @param zoneId | |
* @return array of {@link LocalDateTime} | |
*/ | |
public static LocalDateTime[] convertDates(DateArray dateArray, ZoneId zoneId) { | |
LocalDateTime[] strings = new LocalDateTime[dateArray.getValuesCount()]; | |
for (int i = 0; i < dateArray.getValuesCount(); i++) { | |
Timestamp ts = dateArray.getValues(i); | |
strings[i] = Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()).atZone(zoneId).toLocalDateTime(); | |
} | |
return strings; | |
} | |
/** | |
* Converts a {@link StringArray} to an array of strings | |
* | |
* @param stringArray | |
* @return array of strings | |
*/ | |
public static String[] convertStrings(StringArray stringArray) { | |
String[] strings = new String[stringArray.getValuesCount()]; | |
for (int i = 0; i < stringArray.getValuesCount(); i++) { | |
strings[i] = stringArray.getValues(i); | |
} | |
return strings; | |
} | |
/** | |
* Converts a list of {@link org.eclipse.mdm.protobuf.Mdm.FloatComplex} to an | |
* array of {@link FloatComplex} | |
* | |
* @param valuesList | |
* @return array of {@link FloatComplex} | |
*/ | |
public static FloatComplex[] convertFloatComplex(List<org.eclipse.mdm.protobuf.Mdm.FloatComplex> valuesList) { | |
FloatComplex[] floatComplexes = new FloatComplex[valuesList.size()]; | |
for (int i = 0; i < valuesList.size(); i++) { | |
floatComplexes[i] = new FloatComplex(valuesList.get(i).getRe(), valuesList.get(i).getIm()); | |
} | |
return floatComplexes; | |
} | |
/** | |
* Converts a list of {@link org.eclipse.mdm.protobuf.Mdm.DoubleComplex} to an | |
* array of {@link DoubleComplex} | |
* | |
* @param valuesList | |
* @return array of {@link DoubleComplex} | |
*/ | |
public static DoubleComplex[] convertDoubleComplex(List<org.eclipse.mdm.protobuf.Mdm.DoubleComplex> valuesList) { | |
DoubleComplex[] doubleComplexes = new DoubleComplex[valuesList.size()]; | |
for (int i = 0; i < valuesList.size(); i++) { | |
doubleComplexes[i] = new DoubleComplex(valuesList.get(i).getRe(), valuesList.get(i).getIm()); | |
} | |
return doubleComplexes; | |
} | |
/** | |
* Converts a list of {@link ByteString} to an array of array of byte. | |
* | |
* @param valuesList | |
* @return array of array of byte. | |
*/ | |
public static byte[][] convertByteStreams(List<ByteString> valuesList) { | |
byte[][] byteStreams = new byte[valuesList.size()][]; | |
for (int i = 0; i < valuesList.size(); i++) { | |
byteStreams[i] = valuesList.get(i).toByteArray(); | |
} | |
return byteStreams; | |
} | |
/** | |
* Converts an {@link org.eclipse.mdm.protobuf.Mdm.AxisType} to {@link AxisType} | |
* | |
* @param axisType | |
* @return converted {@link AxisType} | |
*/ | |
public static AxisType convert(Mdm.AxisType axisType) { | |
switch (axisType) { | |
case X_AXIS: | |
return AxisType.X_AXIS; | |
case Y_AXIS: | |
return AxisType.Y_AXIS; | |
case XY_AXIS: | |
return AxisType.XY_AXIS; | |
default: | |
throw new RuntimeException("Invalid value for AxisType: " + axisType.name()); | |
} | |
} | |
/** | |
* Converts an {@link org.eclipse.mdm.protobuf.Mdm.ScalarType} to a | |
* {@link ScalarType}. | |
* | |
* @param scalarType | |
* @return converted {@link ScalarType} | |
*/ | |
public static ScalarType convert(Mdm.ScalarType scalarType) { | |
switch (scalarType) { | |
case STRING: | |
return ScalarType.STRING; | |
case DATE: | |
return ScalarType.DATE; | |
case BOOLEAN: | |
return ScalarType.BOOLEAN; | |
case BYTE: | |
return ScalarType.BYTE; | |
case SHORT: | |
return ScalarType.SHORT; | |
case INTEGER: | |
return ScalarType.INTEGER; | |
case LONG: | |
return ScalarType.LONG; | |
case FLOAT: | |
return ScalarType.FLOAT; | |
case DOUBLE: | |
return ScalarType.DOUBLE; | |
case BYTE_STREAM: | |
return ScalarType.BYTE_STREAM; | |
case FLOAT_COMPLEX: | |
return ScalarType.FLOAT_COMPLEX; | |
case DOUBLE_COMPLEX: | |
return ScalarType.DOUBLE_COMPLEX; | |
case ENUMERATION: | |
return ScalarType.ENUMERATION; | |
case FILE_LINK: | |
return ScalarType.FILE_LINK; | |
case BLOB: | |
return ScalarType.BLOB; | |
case UNKNOWN: | |
return ScalarType.UNKNOWN; | |
default: | |
throw new RuntimeException("Invalid value for ScalarType: " + scalarType.name()); | |
} | |
} | |
/** | |
* Converts a list of {@link MeasuredValues} to a {@link MeasuredValuesList} | |
* | |
* @param measuredValues | |
* @return converted {@link MeasuredValuesList} | |
*/ | |
public static MeasuredValuesList convert(List<MeasuredValues> measuredValues) { | |
MeasuredValuesList.Builder builder = MeasuredValuesList.newBuilder(); | |
for (MeasuredValues m : measuredValues) { | |
builder.addValues(convert(m)); | |
} | |
return builder.build(); | |
} | |
/** | |
* Converts {@link MeasuredValues} to | |
* {@link org.eclipse.mdm.protobuf.Mdm.MeasuredValues} | |
* | |
* @param m | |
* @return converted {@link org.eclipse.mdm.protobuf.Mdm.MeasuredValues} | |
*/ | |
public static Mdm.MeasuredValues convert(MeasuredValues m) { | |
Mdm.MeasuredValues.Builder builder = Mdm.MeasuredValues.newBuilder().setName(m.getName()).setUnit(m.getUnit()) | |
.setLength(m.getLength()).setAxisType(convert(m.getAxisType())).setIndependent(m.isIndependent()) | |
.setScalarType(Mdm.ScalarType.valueOf(m.getScalarType().name())) | |
.addAllGenerationParameters(Doubles.asList(m.getGenerationParameters())); | |
BooleanArray.Builder flags = BooleanArray.newBuilder(); | |
ValueIterator<Object> it = m.iterator(); | |
if (m.getScalarType().isString()) { | |
StringArray.Builder strings = StringArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
strings.addValues((String) it.next()); | |
} | |
builder.setStringArray(strings); | |
} else if (m.getScalarType().isDate()) { | |
DateArray.Builder dates = DateArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
LocalDateTime t = (LocalDateTime) it.next(); | |
Instant time = t.toInstant(ZoneOffset.UTC); | |
dates.addValues( | |
Timestamp.newBuilder().setSeconds(time.getEpochSecond()).setNanos(time.getNano()).build()); | |
} | |
builder.setDateArray(dates); | |
} else if (m.getScalarType().isBoolean()) { | |
BooleanArray.Builder booleans = BooleanArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
booleans.addValues((boolean) it.next()); | |
} | |
builder.setBooleanArray(booleans); | |
} else if (m.getScalarType().isByte()) { | |
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
bytes.write((byte) it.next()); | |
} | |
builder.setByteArray(ByteArray.newBuilder().setValues(ByteString.copyFrom(bytes.toByteArray()))); | |
} else if (m.getScalarType().isShort()) { | |
ShortArray.Builder shorts = ShortArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
shorts.addValues((short) it.next()); | |
} | |
builder.setShortArray(shorts); | |
} else if (m.getScalarType().isInteger()) { | |
IntegerArray.Builder ints = IntegerArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
int i = (int) it.next(); | |
ints.addValues(i); | |
} | |
builder.setIntegerArray(ints); | |
} else if (m.getScalarType().isLong()) { | |
LongArray.Builder ints = LongArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
ints.addValues((long) it.next()); | |
} | |
builder.setLongArray(ints); | |
} else if (m.getScalarType().isFloat()) { | |
FloatArray.Builder floats = FloatArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
floats.addValues((float) it.next()); | |
} | |
builder.setFloatArray(floats); | |
} else if (m.getScalarType().isDouble()) { | |
DoubleArray.Builder doubles = DoubleArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
doubles.addValues((double) it.next()); | |
} | |
builder.setDoubleArray(doubles); | |
} else if (m.getScalarType().isByteStream()) { | |
ByteStreamArray.Builder bytestrs = ByteStreamArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
bytestrs.addValues(ByteString.copyFrom((byte[]) it.next())); | |
} | |
builder.setByteStreamArray(bytestrs); | |
} else if (m.getScalarType().isFloatComplex()) { | |
FloatComplexArray.Builder floats = FloatComplexArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
floats.addValues(convert((FloatComplex) it.next())); | |
} | |
builder.setFloatComplexArray(floats); | |
} else if (m.getScalarType().isDoubleComplex()) { | |
DoubleComplexArray.Builder doubles = DoubleComplexArray.newBuilder(); | |
while (it.hasNext()) { | |
flags.addValues(it.isValid()); | |
doubles.addValues(convert((DoubleComplex) it.next())); | |
} | |
builder.setDoubleComplexArray(doubles); | |
} else { | |
throw new IllegalArgumentException( | |
"MeasuredValues with scalarType '" + m.getScalarType() + "' not supported!"); | |
} | |
builder.addAllFlags(flags.getValuesList()); | |
return builder.build(); | |
} | |
/** | |
* Converts between FloatComplex. | |
* | |
* @param complex | |
* @return | |
*/ | |
public static Mdm.FloatComplex convert(FloatComplex complex) { | |
return Mdm.FloatComplex.newBuilder().setRe(complex.real()).setIm(complex.imaginary()).build(); | |
} | |
/** | |
* Converts between DoubleComplex. | |
* | |
* @param complex | |
* @return | |
*/ | |
public static Mdm.DoubleComplex convert(DoubleComplex complex) { | |
return Mdm.DoubleComplex.newBuilder().setRe(complex.real()).setIm(complex.imaginary()).build(); | |
} | |
/** | |
* Converts between AxisType. | |
* | |
* @param axisType | |
* @return | |
*/ | |
public static Mdm.AxisType convert(AxisType axisType) { | |
return Mdm.AxisType.valueOf(axisType.name()); | |
} | |
/** | |
* Converts between ValuesMode. | |
* | |
* @param valuesMode | |
* @return | |
*/ | |
public static ValuesMode convert(Mdm.ValuesMode valuesMode) { | |
return ValuesMode.valueOf(valuesMode.name()); | |
} | |
/** | |
* Converts between ScalarType. | |
* | |
* @param value | |
* @return | |
*/ | |
public static Mdm.ScalarType convert(ScalarType value) { | |
return Mdm.ScalarType.valueOf(value.name()); | |
} | |
/** | |
* Converts a {@link org.eclipse.mdm.protobuf.Mdm.ReadRequest} to a | |
* {@link ReadRequest} | |
* | |
* @param context | |
* @param protoReadRequest | |
* @return converted {@link ReadRequest} | |
*/ | |
public static ReadRequest convert(ApplicationContext context, Mdm.ReadRequest protoReadRequest) { | |
EntityManager em = context.getEntityManager() | |
.orElseThrow(() -> new ServiceNotProvidedException(EntityManager.class)); | |
ChannelGroup channelGroup = em.load(ChannelGroup.class, protoReadRequest.getChannelGroupId()); | |
ReadRequestBuilder rb = ReadRequest.create(channelGroup); | |
if (protoReadRequest.getChannelIdsCount() == 0) { | |
rb = rb.allChannels(); | |
} else { | |
// Load Channels and group by ID. If multiple Channels with the same ID are | |
// loaded (which would be incorrect data), only the first one is used. | |
Map<String, Optional<Channel>> channels = em.load(Channel.class, protoReadRequest.getChannelIdsList()) | |
.stream().collect(Collectors.groupingBy(Channel::getID, Collectors.reducing((c1, c2) -> c1))); | |
// Load Units and group by ID. If multiple Units with the same ID are | |
// loaded (which would be incorrect data), only the first one is used. | |
Map<String, Optional<Unit>> units = em.load(Unit.class, protoReadRequest.getUnitIdsList()).stream() | |
.collect(Collectors.groupingBy(Unit::getID, Collectors.reducing((u1, u2) -> u1))); | |
for (int i = 0; i < protoReadRequest.getChannelIdsCount(); i++) { | |
String channelId = protoReadRequest.getChannelIds(i); | |
Channel channel = channels.get(channelId).orElseThrow( | |
() -> new IllegalArgumentException("Channel with ID '" + channelId + "' does not exist!")); | |
final String unitId; | |
if (i < protoReadRequest.getUnitIdsCount()) { | |
unitId = protoReadRequest.getUnitIds(i); | |
} else { | |
unitId = null; | |
} | |
Unit unit; | |
if (Strings.isNullOrEmpty(unitId)) { | |
// no unit provided -> use unit from channel | |
unit = channel.getUnit(); | |
} else { | |
unit = units.get(unitId).orElseThrow( | |
() -> new IllegalArgumentException("Unit with ID '" + unitId + "' does not exist!")); | |
} | |
rb = rb.channel(channel, unit); | |
} | |
} | |
return rb.valuesMode(convert(protoReadRequest.getValuesMode())).values(protoReadRequest.getStartIndex(), | |
protoReadRequest.getRequestSize()); | |
} | |
} |