com.credibledoc.iso8583packer.ValueHolder Maven / Gradle / Ivy
Show all versions of iso-8583-packer Show documentation
package com.credibledoc.iso8583packer;
import com.credibledoc.iso8583packer.bcd.BcdBodyPacker;
import com.credibledoc.iso8583packer.bcd.BcdService;
import com.credibledoc.iso8583packer.bitmap.BitmapPacker;
import com.credibledoc.iso8583packer.body.BodyPacker;
import com.credibledoc.iso8583packer.dump.DumpService;
import com.credibledoc.iso8583packer.dump.Visualizer;
import com.credibledoc.iso8583packer.exception.PackerRuntimeException;
import com.credibledoc.iso8583packer.length.LengthPacker;
import com.credibledoc.iso8583packer.message.MsgField;
import com.credibledoc.iso8583packer.message.MsgFieldType;
import com.credibledoc.iso8583packer.message.MsgPair;
import com.credibledoc.iso8583packer.message.MsgValue;
import com.credibledoc.iso8583packer.navigator.Navigator;
import com.credibledoc.iso8583packer.navigator.NavigatorService;
import com.credibledoc.iso8583packer.offset.Offset;
import com.credibledoc.iso8583packer.tag.TagPacker;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* This builder helps to fill the existing {@link MsgField} definition with data. It contains
* two state objects, the {@link #msgValue} and {@link #msgField}.
*
* The {@link MsgField} definition can be created by {@link FieldBuilder}.
*
* @author Kyrylo Semenko
*/
public class ValueHolder {
protected static final int INITIAL_SIZE_100_BYTES = 100;
protected static final String PARTIAL_DUMP = "\nPartial dump:\n";
protected static final String ROOT_MSG_FIELD = "\nRoot MsgField:\n";
protected static final String THE_MSG_FIELD = "The MsgField '";
protected static final String MSG_FIELD = "\nMsgField:\n";
/**
* The builder state. Contains an object created from the {@link #msgField} template. This field will be a part
* of object graph for packing and unpacking this graph from / to a byte array. You can show this graph by calling
* the {@link com.credibledoc.iso8583packer.dump.DumpService#dumpMsgValue(MsgField, MsgValue, boolean)} method.
*/
protected MsgValue msgValue;
/**
* The builder state. Contains an object created by the {@link FieldBuilder} builder. It contains a graph of
* {@link MsgField}s connected to each other. This graph defines rules of packing and unpacking of bytes from / to
* a {@link #msgValue} object graph. You can show this graph by calling
* the {@link com.credibledoc.iso8583packer.dump.DumpService#dumpMsgField(MsgField)} method.
*/
protected MsgField msgField;
/**
* The service helps to navigate through the {@link MsgField} and {@link MsgValue} object graphs.
*/
protected Navigator navigator;
/**
* The service helps to visualize the {@link MsgField} and {@link MsgValue} object graphs.
*/
protected Visualizer visualizer;
/**
* Please do not create instances of this builder. It uses for internal purposes only,
* please use one of the newInstance() methods.
*/
protected ValueHolder() {
// empty
}
/**
* Call the {@link #newInstance(MsgField, boolean)} method with the fromRoot=false value.
*
* @param definition see the {@link #newInstance(MsgField, boolean)} method description.
* @return See the {@link #newInstance(MsgField, boolean)} method description.
*/
public static ValueHolder newInstance(MsgField definition) {
return newInstance(definition, false);
}
/**
* Create a new instance of {@link MsgValue} from the {@link MsgField} definition for filling it with data.
*
* Example of usage:
*
* ValueHolder valueHolder = ValueHolder.newInstance(isoMsgField);
*
*
* How to fill the data to the {@link FieldBuilder}? See the following example:
*
* String mtiValue = "0200";
* valueHolder.jumpToChild(MTI_NAME).setValue(mtiValue);
*
*
* @param definition existing definition created with {@link FieldBuilder}.
* @param fromRoot if 'false', the current MsgField definition will be used as a current position in the
* MsgField graph.
* If 'true', the root definition in the MsgField graph will be used as a current position.
* @return A new instance of the {@link ValueHolder} with {@link #msgValue} and {@link #msgField} in its context.
*/
public static ValueHolder newInstance(MsgField definition, boolean fromRoot) {
if (definition == null) {
throw new PackerRuntimeException("MsgField definition cannot be 'null'.");
}
ValueHolder valueHolder = new ValueHolder();
valueHolder.createDefaultServices();
return valueHolder.setValueAndField(definition, fromRoot);
}
/**
* Call the {@link #newInstance(FieldBuilder, boolean)} method with fromRoot=false value.
*
* @param fieldBuilder see the the {@link #newInstance(FieldBuilder, boolean)} method description.
* @return See the {@link #newInstance(FieldBuilder, boolean)} method description.
*/
public static ValueHolder newInstance(FieldBuilder fieldBuilder) {
return newInstance(fieldBuilder.getCurrentField());
}
/**
* Call the {@link #newInstance(MsgField, boolean)} method.
*
* @param fieldBuilder the current {@link FieldBuilder} with the {@link FieldBuilder#msgField} value.
* @param fromRoot see the {@link #newInstance(MsgField, boolean)} description.
* @return See the {@link #newInstance(MsgField, boolean)} description.
*/
public static ValueHolder newInstance(FieldBuilder fieldBuilder, boolean fromRoot) {
return newInstance(fieldBuilder.getCurrentField(), fromRoot);
}
/**
* Create instances of services used in the builder. The method may be overridden if needed.
*/
protected void createDefaultServices() {
navigator = NavigatorService.getInstance();
visualizer = DumpService.getInstance();
visualizer.setNavigator(navigator);
navigator.setVisualizer(visualizer);
}
/**
* Create a new {@link MsgValue} instance.
*
* @param definition an existing definition.
* @param fromRoot if 'true', the root {@link MsgField} will be used as a definition.
* @return A new instance of the {@link ValueHolder} with {@link #msgValue} and the {@link #msgField} in its context.
*/
protected ValueHolder setValueAndField(MsgField definition, boolean fromRoot) {
if (fromRoot) {
definition = navigator.findRoot(definition);
}
msgField = definition;
msgValue = navigator.newFromNameAndTag(definition);
msgValue.setRoot(msgValue); // root field references to itself
if (msgValue.getParent() != null) {
msgValue.getParent().getChildNamesMap().put(msgValue.getName(), msgValue);
}
return this;
}
/**
* Create a new instance of {@link ValueHolder}.
*
* @param msgValue will be set to the {@link ValueHolder#msgValue}.
* @param msgField will be set to the {@link ValueHolder#msgField}.
* @return The new created {@link ValueHolder} with the {@link #msgValue} and {@link #msgField} in its context.
*/
public static ValueHolder newInstance(MsgValue msgValue, MsgField msgField) {
if (msgValue == null) {
throw new PackerRuntimeException("MsgValue cannot be 'null'.");
}
if (msgField == null) {
throw new PackerRuntimeException("MsgField cannot be 'null'.");
}
ValueHolder valueHolder = new ValueHolder();
valueHolder.createDefaultServices();
return valueHolder.setValueAndField(msgValue, msgField);
}
/**
* Set the arguments to the current instance of the {@link ValueHolder}.
*
* @param msgValue will be set to the {@link ValueHolder#msgValue}.
* @param msgField will be set to the {@link ValueHolder#msgField}.
* @return The current instance of {@link ValueHolder} with the {@link #msgValue} and {@link #msgField} in its context.
*/
protected ValueHolder setValueAndField(MsgValue msgValue, MsgField msgField) {
try {
this.msgValue = msgValue;
this.msgField = msgField;
if (this.msgValue.getName() == null) {
this.msgValue.setName(msgField.getName());
}
if (this.msgValue.getRoot() == null) {
this.msgValue.setRoot(this.msgValue);
}
return this;
} catch (Exception e) {
MsgField rootMsgField = navigator.findRoot(msgField);
throw new PackerRuntimeException("Exception in method get(msgValue, msgField): " + e.getMessage() + "\n" +
"\nThe root MsgField:\n" + visualizer.dumpMsgField(rootMsgField) +
"\nMsgValue:\n" + visualizer.dumpMsgValue(msgField, msgValue, true) + "\n", e);
}
}
/**
* Unpack the bytes started from offset to a new {@link MsgField} using its definition from the third argument.
* @param bytes the source bytes.
* @param offset the index where the field starts in the bytes.
* @param msgField the definition of the {@link MsgField} structure.
* @return The unpacked {@link MsgValue}.
*/
public static MsgValue unpack(byte[] bytes, int offset, MsgField msgField) {
ValueHolder valueHolder = newInstance(msgField);
return valueHolder.unpackMsgField(bytes, offset);
}
/**
* Unpack the bytes started from offset to the {@link #msgField} in the context.
* @param bytes the source bytes.
* @param offset the index where the field starts in the bytes.
* @return The unpacked {@link MsgValue}.
*/
protected MsgValue unpackMsgField(byte[] bytes, int offset) {
MsgValue newMsgValue = msgValue;
try {
Offset offsetObject = new Offset();
offsetObject.setValue(offset);
MsgPair msgPair = new MsgPair(msgField, newMsgValue);
unpackFieldRecursively(bytes, offsetObject, msgPair);
msgValue = newMsgValue;
msgField = msgPair.getMsgField();
if (newMsgValue.getParent() != null) {
newMsgValue.getParent().getChildNamesMap().put(newMsgValue.getName(), newMsgValue);
}
return newMsgValue;
} catch (Exception e) {
String dump = PARTIAL_DUMP + visualizer.dumpMsgValue(msgField, newMsgValue, true) +
ROOT_MSG_FIELD + visualizer.dumpMsgField(navigator.findRoot(msgField));
throw new PackerRuntimeException("Cannot unpack field: " + navigator.generatePath(newMsgValue) + dump, e);
}
}
/**
* @param bytes the source bytes.
* @param offset the index where the field starts in the bytes.
* @param msgPair the definition of the {@link MsgField} structure and the field values.
*/
protected void unpackFieldRecursively(byte[] bytes, Offset offset, MsgPair msgPair) {
navigator.validateSameNamesAndTags(msgPair);
Integer rawDataLength;
MsgFieldType msgFieldType = msgPair.getMsgField().getType();
if (MsgFieldType.MSG == msgFieldType) {
if (isValType(msgPair.getMsgField().getChildren())) {
rawDataLength = sumChildrenLen(msgPair.getMsgField().getChildren());
} else {
rawDataLength = bytes.length - offset.getValue();
}
} else if (msgFieldType == MsgFieldType.BIT_SET) {
rawDataLength = unpackBitSet(bytes, offset, msgPair);
} else if (MsgFieldType.VAL == msgFieldType) {
rawDataLength = unpackFixedLengthType(bytes, offset, msgPair);
} else {
rawDataLength = unpackOtherTypes(bytes, offset, msgPair);
}
MsgValue parent = msgPair.getMsgValue().getParent();
if (parent != null) {
parent.getChildNamesMap().put(msgPair.getMsgValue().getName(), msgPair.getMsgValue());
}
if (msgPair.getMsgField().getChildren() == null) {
// set value, but for leaves (outer children) only.
setValueToLeaf(bytes, offset, msgPair, rawDataLength);
} else {
unpackChildren(bytes, offset, msgPair, rawDataLength);
return;
}
offset.add(rawDataLength);
}
protected int sumChildrenLen(List children) {
int result = 0;
for (MsgField child : children) {
result = result + child.getLen();
}
return result;
}
protected boolean isValType(List children) {
for (MsgField child : children) {
if (child.getType() != MsgFieldType.VAL) {
return false;
}
}
return true;
}
protected Integer unpackOtherTypes(byte[] bytes, Offset offset, MsgPair msgPair) {
Integer rawDataLength = null;
TagPacker tagPacker = navigator.getTagPacker(msgPair.getMsgField());
int tagPackedLength = tagPacker == null ? 0 : tagPacker.getPackedLength();
boolean lengthFirst = MsgFieldType.getLengthFirstTypes().contains(msgPair.getMsgField().getType());
Object tag = null;
if (lengthFirst && MsgFieldType.isLengthType(msgPair.getMsgField())) {
rawDataLength = unpackLength(bytes, offset, msgPair) - tagPackedLength;
}
if (MsgFieldType.getTaggedTypes().contains(msgPair.getMsgField().getType())) {
tag = unpackTag(bytes, offset, msgPair);
}
boolean tagUnpackedButIsDifferent = tag != null && !Objects.equals(tag, msgPair.getMsgField().getTag());
if (tagUnpackedButIsDifferent) {
replaceWithSibling(msgPair, tag);
}
if (MsgFieldType.getTaggedTypes().contains(msgPair.getMsgField().getType())) {
unpackTagBytes(bytes, offset, msgPair, tagPackedLength, tag);
}
if (!lengthFirst && MsgFieldType.isLengthType(msgPair.getMsgField())) {
rawDataLength = unpackLength(bytes, offset, msgPair);
}
if (msgPair.getMsgField().getParent() != null && msgPair.getMsgField().getParent().getChildrenBodyLen() != null) {
rawDataLength = msgPair.getMsgField().getParent().getChildrenBodyLen();
}
if (msgPair.getMsgField().getLen() != null) {
rawDataLength = msgPair.getMsgField().getLen();
}
if (rawDataLength == null) {
throw new PackerRuntimeException("Cannot find rawDataLength of the msgField with path '" + navigator.getPathRecursively(msgPair.getMsgField()) + "'");
}
// unpack field body
unpackBodyBytes(bytes, offset, msgPair, rawDataLength);
return rawDataLength;
}
protected Integer unpackFixedLengthType(byte[] bytes, Offset offset, MsgPair msgPair) {
Integer rawDataLength;
MsgField currentMsgField = msgPair.getMsgField();
rawDataLength = currentMsgField.getLen();
if (rawDataLength == null) {
MsgValue currentMsgValue = msgPair.getMsgValue();
MsgValue currentMsgValueParent = currentMsgValue.getParent();
if (currentMsgValueParent != null && currentMsgValueParent.getLengthBytes() != null) {
LengthPacker parentLengthPacker = currentMsgField.getParent().getLengthPacker();
if (parentLengthPacker == null) {
parentLengthPacker = currentMsgField.getParent().getParent().getChildrenLengthPacker();
}
int parentLength = parentLengthPacker.unpack(currentMsgValueParent.getLengthBytes(), 0);
int childrenLength = calculateLengthUpTo(currentMsgValueParent.getChildren(), currentMsgValue);
rawDataLength = parentLength - childrenLength;
} else {
rawDataLength = bytes.length - offset.getValue();
}
}
int remaining = bytes.length - offset.getValue();
if (rawDataLength > remaining) {
throw new PackerRuntimeException("Cannot unpack bytes because the remaining data length '" + remaining +
"' of the byte array is less than defined 'len' value '" + rawDataLength +
"' of the MsgField with path '" + navigator.getPathRecursively(currentMsgField) + "'.");
}
unpackBodyBytes(bytes, offset, msgPair, rawDataLength);
return rawDataLength;
}
private int calculateLengthUpTo(List siblings, MsgValue currentSibling) {
int length = 0;
for (MsgValue sibling : siblings) {
if (sibling == currentSibling) {
return length;
}
length += sibling.getBodyBytes().length;
}
throw new PackerRuntimeException("It is expected, that the siblings' list contains the current sibling, " +
"but the current sibling cannot be found in the list.\n" +
"Siblings: '" + siblings + "'.\n" +
"Current sibling: '" + currentSibling + "'.");
}
protected LengthPacker getLengthPackerFromParentOrSelfOrThrowException(MsgField msgField) {
boolean parentContainsChildrenLengthPacker = msgField.getParent() != null &&
msgField.getParent().getChildrenLengthPacker() != null;
if (parentContainsChildrenLengthPacker) {
return msgField.getParent().getChildrenLengthPacker();
}
if (msgField.getLengthPacker() == null) {
throw new PackerRuntimeException("Property lengthPacker is not defined. Please define it by calling " +
"the .defineHeaderLengthPacker() method. " +
"Current MsgField: " + navigator.generatePath(msgField));
}
return msgField.getLengthPacker();
}
protected void unpackBodyBytes(byte[] bytes, Offset offset, MsgPair msgPair, Integer rawDataLength) {
try {
byte[] rawData = new byte[rawDataLength];
System.arraycopy(bytes, offset.getValue(), rawData, 0, rawData.length);
msgPair.getMsgValue().setBodyBytes(rawData);
} catch (Exception e) {
String path = navigator.getPathRecursively(msgPair.getMsgField());
throw new PackerRuntimeException("Current MsgField: '" + path + "', source bytes length: '" + bytes.length +
"', offset: '" + offset.getValue() + "', rawDataLength: '" + rawDataLength + "'", e);
}
}
protected void unpackChildren(byte[] bytes, Offset offset, MsgPair msgPair, Integer rawDataLength) {
int offsetWithChildren = offset.getValue() + rawDataLength;
if (rawDataLength > 0) {
msgPair.getMsgValue().setChildren(new ArrayList<>());
}
int childNum = 0;
while (offset.getValue() < offsetWithChildren) {
List children = msgPair.getMsgField().getChildren();
MsgField nextMsgFieldChild = children.get(childNum);
if (children.size() > childNum + 1) {
childNum++; // the number of real children may be higher than defined in MsgField
// for example for repeated values or undefined TLV and LTV values
}
MsgValue nextMsgValueChild = navigator.newFromNameAndTag(nextMsgFieldChild);
msgPair.getMsgValue().getChildren().add(nextMsgValueChild);
nextMsgValueChild.setParent(msgPair.getMsgValue());
nextMsgValueChild.setRoot(nextMsgValueChild.getParent().getRoot());
MsgPair msgPairChild = new MsgPair(nextMsgFieldChild, nextMsgValueChild);
unpackFieldRecursively(bytes, offset, msgPairChild);
}
if (offset.getValue() != offsetWithChildren) {
throw new PackerRuntimeException("Expected end of children is '" + offsetWithChildren +
"' but current offset is '" + offset.getValue() +
"'. These values should be equal.");
}
}
protected void unpackTagBytes(byte[] bytes, Offset offset, MsgPair msgPair, Integer fieldTagLength,
Object tag) {
byte[] tagBytes = new byte[fieldTagLength];
System.arraycopy(bytes, offset.getValue(), tagBytes, 0, tagBytes.length);
offset.add(fieldTagLength);
msgPair.getMsgValue().setTagBytes(tagBytes);
msgPair.getMsgValue().setTag(tag);
}
protected Object unpackTag(byte[] bytes, Offset offset, MsgPair msgPair) {
Object tag;
TagPacker tagPacker = navigator.getTagPacker(msgPair.getMsgField());
if (tagPacker == null) {
tag = msgPair.getMsgField().getTag();
} else {
tag = tagPacker.unpack(bytes, offset.getValue());
}
return tag;
}
protected int unpackBitSet(byte[] bytes, Offset offset, MsgPair msgPair) {
List fieldNums = getFieldNumsFromBitSet(bytes, offset, msgPair);
for (int nextFieldNum : fieldNums) {
MsgField msgFieldChild = findChildByFieldNumUnsafe(msgPair.getMsgField(), nextFieldNum);
// Bits 1 and 65 in a bitmap may be used as flags of a secondary and tertiary bitmaps, as well as flags of used msgFields
if (msgFieldChild == null && (nextFieldNum == 1 || nextFieldNum == 65)) {
continue;
}
if (msgFieldChild == null) {
throw new PackerRuntimeException("Cannot find child with fieldNum '" + nextFieldNum +
"' in the msgField " + navigator.getPathRecursively(msgField));
}
MsgValue msgValueChild = navigator.newFromNameAndTag(msgFieldChild);
List children = msgPair.getMsgValue().getChildren();
if (children == null) {
children = new ArrayList<>();
msgPair.getMsgValue().setChildren(children);
}
children.add(msgValueChild);
msgValueChild.setParent(msgPair.getMsgValue());
msgValueChild.setRoot(msgPair.getMsgValue().getRoot());
msgPair.getMsgValue().getChildNamesMap().put(msgValueChild.getName(), msgValueChild);
MsgPair msgPairChild = new MsgPair(msgFieldChild, msgValueChild);
unpackFieldRecursively(bytes, offset, msgPairChild);
}
return 0;
}
protected int unpackLength(byte[] bytes, Offset offset, MsgPair msgPair) {
LengthPacker lengthPacker = getLengthPackerFromParentOrSelfOrThrowException(msgPair.getMsgField());
int lenLength = lengthPacker.calculateLenLength(bytes, offset.getValue());
byte[] lengthBytes = new byte[lenLength];
System.arraycopy(bytes, offset.getValue(), lengthBytes, 0, lengthBytes.length);
msgPair.getMsgValue().setLengthBytes(lengthBytes);
int rawDataLength = lengthPacker.unpack(bytes, offset.getValue());
offset.add(lenLength);
return rawDataLength;
}
protected void setValueToLeaf(byte[] bytes, Offset offset, MsgPair msgPair, int bodyBytesLength) {
BodyPacker bodyPacker = null;
if (msgPair.getMsgField().getBodyPacker() != null) {
bodyPacker = msgPair.getMsgField().getBodyPacker();
} else if (msgPair.getMsgField().getParent() != null) {
bodyPacker = msgPair.getMsgField().getParent().getChildrenBodyPacker();
}
if (bodyPacker == null) {
String path = navigator.getPathRecursively(msgPair.getMsgField());
throw new PackerRuntimeException("BodyPacker not found for MsgField with path '" + path + "'. " +
"Please call the defineBodyPacker(...) method " +
"of the FieldBuilder class, for example " +
"MsgField subfield35 = FieldBuilder.builder(...).defineBodyPacker(...).");
}
Object bodyValue = bodyPacker.unpack(bytes, offset.getValue(), bodyBytesLength);
msgPair.getMsgValue().setBodyValue(bodyValue);
}
protected void replaceWithSibling(MsgPair msgPair, Object tag) {
MsgPair result = new MsgPair();
MsgField msgFieldSibling = findSiblingByTag(tag, msgPair.getMsgField());
boolean isUndefined = false;
if (msgFieldSibling == null) {
isUndefined = true;
MsgField parent = msgPair.getMsgField().getParent();
if (parent != null && parent.getChildrenTagPacker() != null) {
MsgField undefinedMsgField = new MsgField();
String originalName = msgPair.getMsgField().getName();
Map undefinedSiblings = msgPair.getMsgValue().getParent().getUndefinedChildrenMap();
String cloneName = getCloneName(originalName, undefinedSiblings);
undefinedMsgField.setName(cloneName);
undefinedMsgField.setParent(parent);
undefinedMsgField.setDepth(msgPair.getMsgField().getDepth());
resolveBodyPacker(msgPair, parent, undefinedMsgField);
resolveLengthPacker(msgPair, parent, undefinedMsgField);
resolveTagPacker(msgPair, parent, undefinedMsgField);
resolveLen(msgPair, parent, undefinedMsgField);
MsgFieldType newType = msgPair.getMsgField().getType();
undefinedMsgField.setType(newType);
undefinedMsgField.setTag(tag);
msgFieldSibling = undefinedMsgField;
} else {
throwSiblingNotFound(msgPair, tag);
}
}
MsgValue oldMsgValue = msgPair.getMsgValue();
MsgValue parentMsgValue = oldMsgValue.getParent();
result.setMsgField(msgFieldSibling);
assert msgFieldSibling != null;
MsgValue newMsgValue = navigator.newFromNameAndTag(msgFieldSibling);
if (parentMsgValue != null) {
parentMsgValue.getChildren().remove(oldMsgValue);
parentMsgValue.getChildren().add(newMsgValue);
newMsgValue.setParent(parentMsgValue);
newMsgValue.setRoot(parentMsgValue.getRoot());
parentMsgValue.getChildNamesMap().put(newMsgValue.getName(), newMsgValue);
}
result.setMsgValue(newMsgValue);
newMsgValue.setBitSet(oldMsgValue.getBitSet());
newMsgValue.setTagBytes(oldMsgValue.getTagBytes());
newMsgValue.setLengthBytes(oldMsgValue.getLengthBytes());
if (isUndefined) {
Map undefinedChildrenMap = newMsgValue.getParent().getUndefinedChildrenMap();
undefinedChildrenMap.put(newMsgValue.getName(), newMsgValue);
}
msgPair.setMsgField(result.getMsgField());
msgPair.setMsgValue(result.getMsgValue());
}
protected void resolveLen(MsgPair msgPair, MsgField parent, MsgField undefinedMsgField) {
Integer len;
if (msgPair.getMsgField().getLen() != null) {
len = msgPair.getMsgField().getLen();
} else {
len = parent.getChildrenBodyLen();
}
undefinedMsgField.setLen(len);
}
protected void resolveTagPacker(MsgPair msgPair, MsgField parent, MsgField undefinedMsgField) {
TagPacker newTagPacker;
if (msgPair.getMsgField().getTagPacker() != null) {
newTagPacker = msgPair.getMsgField().getTagPacker();
} else {
newTagPacker = parent.getChildrenTagPacker();
}
undefinedMsgField.setTagPacker(newTagPacker);
}
protected void resolveLengthPacker(MsgPair msgPair, MsgField parent, MsgField undefinedMsgField) {
LengthPacker newLengthPacker;
if (msgPair.getMsgField().getLengthPacker() != null) {
newLengthPacker = msgPair.getMsgField().getLengthPacker();
} else {
newLengthPacker = parent.getChildrenLengthPacker();
}
undefinedMsgField.setLengthPacker(newLengthPacker);
}
protected void resolveBodyPacker(MsgPair msgPair, MsgField parent, MsgField undefinedMsgField) {
BodyPacker newBodyPacker;
if (msgPair.getMsgField().getBodyPacker() != null) {
newBodyPacker = msgPair.getMsgField().getBodyPacker();
} else {
newBodyPacker = parent.getChildrenBodyPacker();
}
undefinedMsgField.setBodyPacker(newBodyPacker);
}
protected String getCloneName(String originalName, Map undefinedChildrenMap) {
return originalName + "-clone-" + (undefinedChildrenMap.size() + 1);
}
protected List getFieldNumsFromBitSet(byte[] bytes, Offset offset, MsgPair msgPair) {
// unpack
BitmapPacker bitmapPacker = msgPair.getMsgField().getBitMapPacker();
if (bitmapPacker == null) {
throw new PackerRuntimeException("Please call the defineHeaderBitmapPacker(...) " +
"method for this field " + navigator.getPathRecursively(msgPair.getMsgValue()));
}
int consumed = bitmapPacker.unpack(msgPair.getMsgValue(), bytes, offset.getValue());
byte[] bitMapBytes = new byte[consumed];
System.arraycopy(bytes, offset.getValue(), bitMapBytes, 0, consumed);
offset.add(consumed);
msgPair.getMsgValue().setBodyBytes(bitMapBytes);
return getFieldNumsAndValidateBitSet(msgPair);
}
protected MsgField findChildByFieldNumUnsafe(MsgField msgField, int nextFieldNum) {
for (MsgField child : msgField.getChildren()) {
if (nextFieldNum == child.getFieldNum()) {
return child;
}
}
return null;
}
protected List getFieldNumsAndValidateBitSet(MsgPair msgPair) {
BitSet unpackedBitSet = msgPair.getMsgValue().getBitSet();
List fieldNums = new ArrayList<>();
int maxFieldNum = getMaxFieldNum(msgPair.getMsgField().getChildren(), msgPair.getMsgField());
boolean secondaryBitmapMarked = false;
boolean tertiaryBitmapMarked = false;
for (int nextFieldNum = 1; nextFieldNum <= maxFieldNum; nextFieldNum++) {
MsgField childMsgField = navigator.findByFieldNum(msgPair.getMsgField().getChildren(), nextFieldNum);
boolean bitMarked = unpackedBitSet.get(nextFieldNum);
// Bits 1 and 65 in a bitmap may be used as flags of a secondary and tertiary bitmaps, as well as flags of used msgFields
if (!bitMarked || (childMsgField == null && (nextFieldNum == 65 || nextFieldNum == 1))) {
continue;
}
if (childMsgField == null) {
String path = navigator.getPathRecursively(msgPair.getMsgField());
throw new PackerRuntimeException("Unpacked bitSet contains fieldNum '" + nextFieldNum + "', " +
"but the MsgField with path '" + path + "' has no child with such fieldNum. " +
"Please set the defineFieldNum(" + nextFieldNum + ") value " +
"to one of the field '" + path + "' children.");
}
secondaryBitmapMarked = markSecondaryBitmap(fieldNums, secondaryBitmapMarked, nextFieldNum);
tertiaryBitmapMarked = markTertiaryBitmap(fieldNums, tertiaryBitmapMarked, nextFieldNum);
fieldNums.add(nextFieldNum);
}
return fieldNums;
}
protected boolean markTertiaryBitmap(List fieldNums, boolean tertiaryBitmapMarked, int nextFieldNum) {
if (nextFieldNum > 129 && !tertiaryBitmapMarked) {
int tertiaryFlagIndex = findTertiaryFlagIndex(fieldNums);
fieldNums.add(tertiaryFlagIndex, 65);
tertiaryBitmapMarked = true;
}
return tertiaryBitmapMarked;
}
protected boolean markSecondaryBitmap(List fieldNums, boolean secondaryBitmapMarked, int nextFieldNum) {
if (nextFieldNum > 65 && nextFieldNum < 130 && !secondaryBitmapMarked) {
fieldNums.add(0, 1);
secondaryBitmapMarked = true;
}
return secondaryBitmapMarked;
}
protected int findTertiaryFlagIndex(List fieldNums) {
int index = 0;
for (int num : fieldNums) {
if (num > 64) {
return index;
}
index++;
}
return fieldNums.size();
}
protected void throwSiblingNotFound(MsgPair paramMsgPair, Object tag) {
MsgField paramMsgField = paramMsgPair.getMsgField();
MsgValue paramMsgValue = paramMsgPair.getMsgValue();
String previousFieldCause = "";
MsgField parentMsgField = paramMsgField.getParent();
if (parentMsgField != null && parentMsgField.getLengthPacker() != null) {
previousFieldCause = " \nNext cause is incorrect implementation of the " +
parentMsgField.getLengthPacker().getClass().getSimpleName() +
" class of the previous '" +
navigator.getPathRecursively(parentMsgField) + "' field.";
}
String tagPackerClass = "null";
TagPacker tagPacker = navigator.getTagPacker(paramMsgField);
if (tagPacker != null) {
tagPackerClass = tagPacker.getClass().getSimpleName();
}
throw new PackerRuntimeException("Cannot find a sibling with tag '" + tag +
"' for the '" + navigator.getPathRecursively(paramMsgValue) +
"' field. Its parent '" + navigator.getPathRecursively(parentMsgField) +
"' has no child with such tag." +
"\nThere are few possible causes of this error. " +
"\nFirst cause is an incorrect implementation of " + tagPackerClass +
".unpack() method used for unpacking a tag from bytes. " +
"\nSecond cause is an undefined (unknown) child with tag '" + tag +
"' and parent '" + navigator.getPathRecursively(parentMsgField) +
"' in the MsgField definition. Unknown TLV and LTV tags should have tag, length and body packers in the parent."
+ previousFieldCause + " " +
"\nNext cause may be an incorrect order of tag and length subfields." +
"\nNext cause is wrong place of actual context inside the rootMsgField, actual place is " +
navigator.getPathRecursively(paramMsgField) + ".");
}
protected int getMaxFieldNum(List children, MsgField parent) {
int result = 0;
for (MsgField nextMsgField : children) {
Integer fieldNum = nextMsgField.getFieldNum();
if (fieldNum == null) {
String path = navigator.getPathRecursively(nextMsgField);
throw new PackerRuntimeException("The MsgField with path '" + path + "' " +
"has no 'fieldNum' property defined. " +
"Please call the defineFieldNum(...) value to the MsgField. " +
"The value is mandatory because its parent has the '" + MsgFieldType.class.getSimpleName() +
"." + parent.getType() + "' type.");
}
if (fieldNum > result) {
result = fieldNum;
}
}
return result;
}
protected MsgField findSiblingByTag(Object tag, MsgField msgField) {
MsgField parent = msgField.getParent();
for (MsgField child : parent.getChildren()) {
if (Objects.equals(child.getTag(), tag)) {
return child;
}
}
return null;
}
/**
* Create a new instance of {@link ValueHolder} and set msgPair values to its
* {@link #msgField} and {@link #msgValue} context objects.
* @param msgPair contains {@link MsgField} and {@link MsgValue}.
* @return The created instance.
*/
public static ValueHolder newInstance(MsgPair msgPair) {
ValueHolder valueHolder = new ValueHolder();
valueHolder.createDefaultServices();
valueHolder.msgField = msgPair.getMsgField();
if (valueHolder.msgField == null) {
throw new PackerRuntimeException("MsgField cannot be 'null'.");
}
valueHolder.msgValue = msgPair.getMsgValue();
if (valueHolder.msgValue == null) {
throw new PackerRuntimeException("MsgValue cannot be 'null'.");
}
return valueHolder;
}
/**
* Change the current {@link #msgField} and {@link #msgValue} to the new location in the {@link #msgField} graph.
*
* The new location will be the child with given name from {@link #msgField}
* and the first child of the {@link #msgValue} with the name.
*
* If the current {@link #msgValue} doesn't have the child, it will be created. Else the found child will be set
* tho the {@link #msgValue} field.
*
* @param childName the {@link #msgField}'s child.
* @return The current instance of {@link ValueHolder}.
*/
public ValueHolder jumpToChild(String childName) {
try {
MsgField msgFieldChild = navigator.getChildOrThrowException(childName, msgField);
MsgValue msgValueChild = msgValue.getChildNamesMap().get(childName);
if (msgValueChild == null) {
msgValueChild = createChildren(childName);
}
this.msgValue = msgValueChild;
this.msgField = msgFieldChild;
return this;
} catch (Exception e) {
MsgField rootMsgField = navigator.findRoot(msgField);
if (e instanceof PackerRuntimeException) {
throw new PackerRuntimeException("Message: " + e.getMessage(), e);
} else {
String dump = "\nCannot find a child." + ROOT_MSG_FIELD + visualizer.dumpMsgField(rootMsgField);
throw new PackerRuntimeException("Exception message: " + e.getMessage() + dump, e);
}
}
}
protected MsgValue createChildren(String childName) {
List msgFieldChildren = msgField.getChildren();
if (msgValue.getChildren() == null) {
msgValue.setChildren(new ArrayList<>());
}
List msgValueChildren = msgValue.getChildren();
Map childNamesMap = msgValue.getChildNamesMap();
MsgValue msgValueChild = null;
for (int i = 0; i < msgFieldChildren.size(); i++) {
MsgField nextMsgField = msgFieldChildren.get(i);
MsgValue newMsgValue;
if (msgValueChildren.size() < i + 1) {
newMsgValue = createMsgValue(nextMsgField);
msgValueChildren.add(newMsgValue);
} else if (!nextMsgField.getName().equals(msgValueChildren.get(i).getName())) {
newMsgValue = createMsgValue(nextMsgField);
msgValueChildren.add(i, newMsgValue);
} else {
newMsgValue = msgValueChildren.get(i);
if (!childNamesMap.containsKey(newMsgValue.getName())) {
childNamesMap.put(newMsgValue.getName(), newMsgValue);
}
}
if (newMsgValue.getName().equals(childName)) {
msgValueChild = newMsgValue;
}
}
if (msgValueChild == null) {
throw new PackerRuntimeException("Cannot create MsgValue with name " + childName);
}
return msgValueChild;
}
protected MsgValue createMsgValue(MsgField nextMsgField) {
MsgValue newMsgValue = navigator.newFromNameAndTag(nextMsgField);
newMsgValue.setParent(msgValue);
newMsgValue.setRoot(msgValue.getRoot());
msgValue.getChildNamesMap().put(newMsgValue.getName(), newMsgValue);
return newMsgValue;
}
/**
* Change actual {@link #msgField} and {@link #msgValue} to their parents.
* @return The current instance of {@link ValueHolder}.
*/
public ValueHolder jumpToParent() {
if (msgField.getParent() == null) {
throw new PackerRuntimeException("MsgField '" + navigator.getPathRecursively(msgField) + "' has no parent.");
}
if (msgValue.getParent() == null) {
throw new PackerRuntimeException("MsgValue '" + navigator.getPathRecursively(msgValue) + "' has no parent.");
}
msgField = msgField.getParent();
msgValue = msgValue.getParent();
return this;
}
/**
* Set the {@link MsgValue#setBodyValue(Object)} from the argument to the {@link #msgValue}.
*
* Example of usage:
*
* ValueHolder.newInstance(msgPair)
* .jumpToChild("child_name")
* .setValue("some_value");
*
*
*
* Set the {@link MsgValue#setBodyBytes(byte[])} from the bodyValue to the field, see the
* {@link #setBytes(Object)} method description.
*
* Length and tag are not mandatory.
*
* @param bodyValue can be 'null' for deletion the current msgField. In the case current positions of the
* {@link #msgField} and {@link #msgValue} object graphs will be changed to theirs parents.
* @return The current {@link ValueHolder} with the same {@link #msgValue} and {@link #msgField} in its context.
*/
public ValueHolder setValue(Object bodyValue) {
if (msgField.getChildren() != null && !msgField.getChildren().isEmpty()) {
throw new PackerRuntimeException("Cannot set bodyValue to fields with children. Values can only be set to " +
"leaf fields. Field: " + navigator.getPathRecursively(msgField) + ", bodyValue: " + bodyValue);
}
if (bodyValue == null) {
msgValue.setBodyBytes(null);
msgValue.setLengthBytes(null);
msgValue.setBodyValue(null);
return this;
}
try {
msgValue.setBodyValue(bodyValue);
byte[] valueBytes = setBytes(bodyValue);
setTagAndLenBytes(valueBytes, msgValue, msgField);
return this;
} catch (Exception e) {
MsgValue rootMsgValue = navigator.findRoot(msgValue);
MsgField rootMsgField = navigator.findRoot(msgField);
MsgField appropriateMsgField = navigator.findByNameAndTagOrThrowException(rootMsgField, rootMsgValue);
throw new PackerRuntimeException("Exception message: " + e.getMessage() + "\nCannot set bodyValue" +
" to field '" + navigator.getPathRecursively(msgField) + "'" +
"\nRoot MsgValue:\n" + visualizer.dumpMsgValue(appropriateMsgField, rootMsgValue, true) +
"\nThe MsgField:\n" + visualizer.dumpMsgField(msgField) +
ROOT_MSG_FIELD + visualizer.dumpMsgField(rootMsgField), e);
}
}
/**
* Call the {@link #setValue(Object)} method. Current positions in the {@link #msgField} and {@link #msgValue}
* object graphs remain unchanged.
*
* @param bodyValue see the {@link #setValue(Object)} method description
* @param absolutePath the {@link MsgValue#getName()}s from the root field
* @return See the {@link #setValue(Object)} method description
*/
public ValueHolder setValue(Object bodyValue, String... absolutePath) {
MsgField currentMsgField = msgField;
MsgValue currentMsgValue = msgValue;
jumpAbsolute(absolutePath);
setValue(bodyValue);
this.msgField = currentMsgField;
this.msgValue = currentMsgValue;
return this;
}
/**
* Call the {@link #setValue(Object, String...)} method.
*
* @param bodyValue see the {@link #setValue(Object, String...)} method description
* @param absolutePath the field names from the root field
* @return See the {@link #setValue(Object, String...)} method description
*/
public ValueHolder setValue(Object bodyValue, List absolutePath) {
return setValue(bodyValue, absolutePath.toArray(new String[0]));
}
protected byte[] setBytes(Object bodyValue) {
BodyPacker bodyPacker = msgField.getBodyPacker();
if (bodyPacker == null && msgField.getParent() != null) {
bodyPacker = msgField.getParent().getChildrenBodyPacker();
}
if (bodyPacker == null) {
throw new PackerRuntimeException("BodyPacker not found. Please call setBodyPacker(...) " +
"method\n" +
"MsgField: " + navigator.getPathRecursively(msgField));
}
byte[] bodyBytes;
int bodyLength = bodyPacker.getPackedLength(bodyValue);
Integer exactlyLength = msgField.getExactlyLength();
if (exactlyLength != null && bodyLength != exactlyLength) {
throw new PackerRuntimeException(THE_MSG_FIELD + navigator.getPathRecursively(msgField) +
"' contains the 'exactlyLength' definition with value '" + exactlyLength +
"', but the bodyValue length '" + bodyLength + "' is not the same.");
}
Integer maxLen = msgField.getMaxLen();
if (maxLen != null && maxLen < bodyLength) {
throw new PackerRuntimeException(THE_MSG_FIELD + navigator.getPathRecursively(msgField) +
"' contains the 'maxLen' definition with value '" + maxLen +
"', but its bodyValue length '" + bodyLength + "' is greater.");
}
Integer len = msgField.getLen();
if (len != null && bodyLength != len) {
throw new PackerRuntimeException(THE_MSG_FIELD + navigator.getPathRecursively(msgField) +
"' contains the 'len' definition with value '" + len +
"', but its bodyValue length '" + bodyLength + "' is different.");
}
bodyBytes = new byte[bodyLength];
bodyPacker.pack(bodyValue, bodyBytes, 0);
msgValue.setBodyBytes(bodyBytes);
return bodyBytes;
}
/**
* Set valueBytes to the msgValue
* @param valueBytes source data
* @param msgValue target object
* @param msgField the field definition
*/
protected void setTagAndLenBytes(byte[] valueBytes, MsgValue msgValue, MsgField msgField) {
MsgField msgFieldParent = msgField.getParent();
LengthPacker lengthPacker;
Object tag = this.msgValue.getTag();
if (msgField.getType() == MsgFieldType.LEN_TAG_VAL) {
lengthPacker = msgField.getLengthPacker();
if (lengthPacker == null) {
assert msgFieldParent != null;
lengthPacker = msgFieldParent.getChildrenLengthPacker();
}
packTagBytes(msgField, msgValue, tag);
int tagAndValueLength = valueBytes.length + msgValue.getTagBytes().length;
byte[] lengthBytes = lengthPacker.pack(tagAndValueLength);
msgValue.setLengthBytes(lengthBytes);
} else {
lengthPacker = msgField.getLengthPacker();
if (lengthPacker == null && msgField.getParent() != null) {
lengthPacker = msgField.getParent().getChildrenLengthPacker();
}
if (MsgFieldType.getTaggedTypes().contains(msgField.getType())) {
packTagBytes(msgField, msgValue, tag);
}
if (msgField.getLen() == null && lengthPacker != null) {
byte[] lengthBytes = lengthPacker.pack(valueBytes.length);
msgValue.setLengthBytes(lengthBytes);
}
}
}
protected void packTagBytes(MsgField msgField, MsgValue msgValue, Object tag) {
TagPacker tagPacker = navigator.getTagPacker(msgField);
if (tagPacker == null) {
msgValue.setTagBytes(new byte[0]);
return;
}
byte[] tagBytes = tagPacker.pack(tag);
msgValue.setTagBytes(tagBytes);
}
/**
* Change the current {@link #msgValue} and {@link #msgField} to the new location. If the field sibling is not found,
* create a new one.
*
* @param siblingName the sibling name of the current {@link #msgValue} and {@link #msgField}.
* @return The current builder instance with the new {@link #msgValue} and {@link #msgField}.
*/
public ValueHolder jumpToSibling(String siblingName) {
try {
MsgField msgFieldSibling = navigator.getSiblingOrThrowException(siblingName, msgField);
MsgValue parentMsgValue = msgValue.getParent();
msgValue = parentMsgValue.getChildNamesMap().get(siblingName);
if (msgValue == null) {
throw new PackerRuntimeException("Cannot find child MsgValue with name '" + siblingName +
"' for parent " + parentMsgValue);
}
msgField = msgFieldSibling;
return this;
} catch (Exception e) {
if (msgValue != null && msgField != null) {
MsgValue rootMsgValue = navigator.findRoot(msgValue);
MsgField rootMsgField = navigator.findRoot(msgField);
throw new PackerRuntimeException("Exception: " + e.getMessage() + "\nCannot jumpToSibling '" + siblingName +
"' of the message definition." +
"\nMsgValue:\n" + visualizer.dumpMsgValue(rootMsgField, rootMsgValue, true) +
MSG_FIELD + visualizer.dumpMsgField(rootMsgField) + "\n", e);
}
throw e;
}
}
protected MsgValue remove(String name, List msgValueChildren) {
for (MsgValue child : msgValueChildren) {
if (name.equals(child.getName())) {
msgValueChildren.remove(child);
return child;
}
}
return null;
}
/**
* @return The {@link #msgValue} value;
*/
public MsgValue getCurrentMsgValue() {
return msgValue;
}
/**
* @return The {@link #msgField} value;
*/
public MsgField getCurrentMsgField() {
return msgField;
}
/**
* Add subfields to the current {@link #msgValue}. Old children will be deleted.
*
* @param subfields new children
* @return The current instance with {@link #msgValue} and {@link #msgField} in its context.
*/
public ValueHolder setChildren(List subfields) {
msgValue.getChildNamesMap().clear();
for (MsgValue child : subfields) {
child.setParent(msgValue);
msgValue.getChildNamesMap().put(child.getName(), child);
child.setRoot(msgValue.getRoot());
}
msgValue.setChildren(subfields);
return this;
}
/**
* Navigate to the root {@link #msgValue} and {@link #msgField}.
* @return The current instance of {@link ValueHolder} with root of {@link #msgValue} and corresponding node of
* {@link #msgField} in its context.
*/
public ValueHolder jumpToRoot() {
msgValue = msgValue.getRoot();
msgField = msgField.getRoot();
return this;
}
/**
* Pack this {@link #msgValue} to bytes for sending to Host.
* @return Bytes created from the {@link #msgValue}.
*/
public byte[] pack() {
try {
ByteArrayOutputStream result = packRecursively(msgValue, msgField);
return result.toByteArray();
} catch (Exception e) {
if (msgField != null && msgValue != null) {
MsgValue rootMsgValue = navigator.findRoot(msgValue);
MsgField rootMsgField = navigator.findRoot(msgField);
if (Objects.equals(rootMsgValue.getName(), rootMsgField.getName()) &&
Objects.equals(rootMsgValue.getTag(), rootMsgField.getTag())) {
throw new PackerRuntimeException("Exception: " + e.getMessage() + "\n" +
"Cannot pack field '" + navigator.getPathRecursively(msgValue) + "'" +
PARTIAL_DUMP + visualizer.dumpMsgValue(rootMsgField, rootMsgValue, true) +
MSG_FIELD + visualizer.dumpMsgField(msgField) + "\n", e);
}
}
throw new PackerRuntimeException(e);
}
}
protected ByteArrayOutputStream packRecursively(MsgValue msgValue, MsgField msgField) throws IOException {
int maxLen = msgField.getMaxLen() != null ? msgField.getMaxLen() : INITIAL_SIZE_100_BYTES;
ByteArrayOutputStream messageBytes = new ByteArrayOutputStream(maxLen);
if (msgValue.getChildren() != null) {
ByteArrayOutputStream childrenBytes = new ByteArrayOutputStream(maxLen);
if (MsgFieldType.BIT_SET == msgField.getType()) {
packBitmap(msgValue, msgField, messageBytes);
}
for (MsgValue nextMsgValue : msgValue.getChildren()) {
MsgField msgFieldChild = navigator.findByName(msgField.getChildren(), nextMsgValue.getName());
ByteArrayOutputStream childArray = packRecursively(nextMsgValue, msgFieldChild);
childArray.writeTo(childrenBytes);
}
byte[] bytes = childrenBytes.toByteArray();
if (bytes.length != 0) {
packBodyBytesAndLengthAndTag(msgValue, msgField, messageBytes, bytes);
}
childrenBytes.writeTo(messageBytes);
} else {
if (msgValue.getBodyBytes() != null) {
// Do not pack empty fields
ByteArrayOutputStream headerAndValue = packHeaderAndValue(msgValue, msgField);
headerAndValue.writeTo(messageBytes);
}
}
return messageBytes;
}
protected void packBitmap(MsgValue msgValue, MsgField msgField, ByteArrayOutputStream messageBytes) throws IOException {
BitmapPacker bitmapPacker = msgField.getBitMapPacker();
if (bitmapPacker == null) {
throw new PackerRuntimeException("The value of '" + BitmapPacker.class.getSimpleName() +
"' type is mandatory for '" + MsgFieldType.class.getSimpleName() +
"' '" + MsgFieldType.BIT_SET + "' type. " +
"Please call the defineHeaderBitmapPacker(...) method.");
}
BitSet bitSet = new BitSet();
int maxFieldNum = 0;
for (MsgField nextMsgField : msgField.getChildren()) {
Integer fieldNum = nextMsgField.getFieldNum();
MsgValue nextMsgValue = navigator.findByFieldNum(msgValue.getChildren(), fieldNum);
if (nextMsgValue != null &&
(nextMsgValue.getBodyValue() != null ||
(nextMsgValue.getChildren() != null && !nextMsgValue.getChildren().isEmpty())
)
) {
maxFieldNum = Math.max(maxFieldNum, fieldNum);
bitSet.set(fieldNum);
}
}
if (maxFieldNum > 64) {
bitSet.set(1);
}
if (maxFieldNum > 128) {
bitSet.set(65);
}
msgValue.setBitSet(bitSet);
byte[] bytes = bitmapPacker.pack(bitSet);
msgValue.setBodyBytes(bytes);
messageBytes.write(bytes);
}
protected void packBodyBytesAndLengthAndTag(MsgValue msgValue, MsgField msgField, ByteArrayOutputStream messageBytes, byte[] bytes) throws IOException {
msgValue.setBodyBytes(bytes);
LengthPacker lengthPacker;
boolean lengthPrecedesTag = msgField.getType() == MsgFieldType.LEN_TAG_VAL;
if (MsgFieldType.getTaggedTypes().contains(msgField.getType())) {
setTagBytes(msgValue, msgField);
}
if (MsgFieldType.getLengthTypes().contains(msgField.getType())) {
lengthPacker = getLengthPackerFromParentOrSelfOrThrowException(msgField);
packLength(msgValue, lengthPacker, bytes, lengthPrecedesTag);
}
if (lengthPrecedesTag) {
writeLengthBytesIfAllowed(msgValue, msgField, messageBytes);
writeTagBytesIfAllowed(msgValue, msgField, messageBytes);
} else {
writeTagBytesIfAllowed(msgValue, msgField, messageBytes);
writeLengthBytesIfAllowed(msgValue, msgField, messageBytes);
}
}
protected void writeTagBytesIfAllowed(MsgValue msgValue, MsgField msgField, ByteArrayOutputStream messageBytes) throws IOException {
if (MsgFieldType.getTaggedTypes().contains(msgField.getType())) {
messageBytes.write(msgValue.getTagBytes());
}
}
protected void writeLengthBytesIfAllowed(MsgValue msgValue, MsgField msgField,
ByteArrayOutputStream messageBytes) throws IOException {
if (MsgFieldType.getLengthTypes().contains(msgField.getType())) {
messageBytes.write(msgValue.getLengthBytes());
}
}
protected ByteArrayOutputStream packHeaderAndValue(MsgValue msgValue, MsgField msgField) throws IOException {
if (msgValue.getBodyBytes() == null) {
throw new PackerRuntimeException("Expected non-null MsgValue.bodyBytes but found 'null'. MsgValue path: '" +
navigator.getPathRecursively(msgValue) + "'. Please set the value or delete the MsgField. " +
"The cause of the exception probably in the setValue() method.");
}
int length = msgValue.getBodyBytes().length;
ByteArrayOutputStream result;
length += msgValue.getTagBytes() == null ? 0 : msgValue.getTagBytes().length;
length += msgValue.getLengthBytes() == null ? 0 : msgValue.getLengthBytes().length;
result = new ByteArrayOutputStream(length);
boolean lengthPrecedesTag = msgField.getType() == MsgFieldType.LEN_TAG_VAL;
if (lengthPrecedesTag) {
writeLengthBytesIfExist(result, msgValue);
writeTagBytes(result, msgValue);
} else {
writeTagBytes(result, msgValue);
writeLengthBytesIfExist(result, msgValue);
}
if (msgValue.getBodyBytes() != null) {
result.write(msgValue.getBodyBytes());
}
return result;
}
protected void writeTagBytes(ByteArrayOutputStream result, MsgValue msgValue) throws IOException {
if (msgValue.getTagBytes() != null) {
result.write(msgValue.getTagBytes());
}
}
protected void writeLengthBytesIfExist(ByteArrayOutputStream result, MsgValue msgValue) throws IOException {
if (msgValue.getLengthBytes() != null) {
result.write(msgValue.getLengthBytes());
}
}
protected void packLength(MsgValue msgValue, LengthPacker lengthPacker, byte[] bytes, boolean lengthPrecedesTag) {
int bytesLength;
if (lengthPrecedesTag) {
bytesLength = bytes.length + msgValue.getTagBytes().length;
} else {
bytesLength = bytes.length;
}
byte[] lengthBytes = lengthPacker.pack(bytesLength);
msgValue.setLengthBytes(lengthBytes);
}
protected void setTagBytes(MsgValue msgValue, MsgField msgField) {
Object tag = msgValue.getTag();
TagPacker tagPacker = navigator.getTagPacker(msgField);
assert tagPacker != null;
byte[] tagBytes = tagPacker.pack(tag);
msgValue.setTagBytes(tagBytes);
}
/**
* Create a copy from the current {@link #msgValue} and set it to this {@link #msgValue} context.
* This sibling will have the same {@link MsgField#getName()}, {@link MsgField#getTag()}
* and {@link MsgField#getFieldNum()} as its sibling.
*
* It is useful for creation of multiple repeated fields with the same name and/or fieldNum.
*
* @return The current actual {@link ValueHolder}.
*/
public ValueHolder cloneSibling() {
MsgValue clone = navigator.newFromNameAndTag(msgField);
clone.setParent(msgValue.getParent());
clone.setRoot(msgValue.getParent().getRoot());
msgValue.getParent().getChildren().add(clone);
clone.getParent().getChildNamesMap().put(clone.getName(), clone);
msgValue = clone;
return this;
}
/**
* Create the new instance and set {@link #msgValue} and {@link #msgField} from the current instance.
*
* @return Created instance of {@link ValueHolder}.
*/
public ValueHolder copyValueHolder() {
ValueHolder clone = new ValueHolder();
clone.createDefaultServices();
clone.msgValue = msgValue;
clone.msgField = msgField;
return clone;
}
/**
* Call the {@link #unpack(byte[], int, MsgField)} method with offset 0 and with current {@link #msgField} from the
* {@link ValueHolder} context.
*
* @param bytes will be set as first argument
* @return The {@link MsgValue} unpacked from the bytes.
*/
public MsgValue unpack(byte[] bytes) {
unpackMsgField(bytes, 0);
return msgValue;
}
/**
* @return Existing {@link #msgField} and {@link #msgValue} created previously by the {@link #newInstance(MsgField)} method.
*/
public MsgPair getCurrentPair() {
return new MsgPair(this.msgField, this.msgValue);
}
public void validateData() {
try {
validateRecursively(msgField, msgValue);
List msgFieldChildren = msgField.getChildren();
if (msgFieldChildren != null) {
for (MsgField childMsgField : msgFieldChildren) {
MsgValue childMsgValue = navigator.findByName(msgValue.getChildren(),
childMsgField.getName());
if (childMsgValue != null) {
validateRecursively(childMsgField, childMsgValue);
}
}
}
} catch (Exception e) {
String dump = PARTIAL_DUMP + visualizer.dumpMsgValue(msgField, msgValue, true) +
ROOT_MSG_FIELD + visualizer.dumpMsgField(navigator.findRoot(msgField));
throw new PackerRuntimeException("Validation failed: " + dump, e);
}
}
protected void validateRecursively(MsgField msgField, MsgValue msgValue) {
if (msgField.getBodyPacker() instanceof BcdBodyPacker) {
BcdService.validateIsStringBcdNumber(msgValue);
}
}
/**
* @param type expected type of returned value
* @param expected type of returned value
* @return The {@link MsgValue#getBodyValue()} casted to T type
*/
public T getValue(Class type) {
return type.cast(msgValue.getBodyValue());
}
/**
* @param navigator see the {@link #navigator} field description.
*/
public void setNavigator(Navigator navigator) {
this.navigator = navigator;
}
/**
* @return the current {@link #navigator}.
*/
public Navigator getNavigator() {
return this.navigator;
}
/**
* @param visualizer see the {@link #visualizer} field description.
*/
public void setVisualizer(Visualizer visualizer) {
this.visualizer = visualizer;
}
/**
* @return The {@link #visualizer} value.
*/
public Visualizer getVisualizer() {
return visualizer;
}
/**
* Change the actual {@link #msgValue} object graph place (location) to be the same as the {@link #msgField},
* see the {@link Navigator#synchronizeMessageValue(MsgField, MsgValue)} method description.
*
* @return The current {@link ValueHolder} instance with adjusted {@link #msgValue}.
*/
public ValueHolder adjust() {
msgValue = this.navigator.synchronizeMessageValue(msgField, msgValue);
return this;
}
/**
* Change the actual object graph places (locations) of the {@link #msgField} and {@link #msgValue}.
*
* @param fieldNames the {@link MsgField#getName()}s of fields from the root of the {@link #msgField}.
* @return The current {@link ValueHolder} instance with a new place (location) of the {@link #msgValue}.
*/
public ValueHolder jumpAbsolute(String ... fieldNames) {
jumpToRoot();
if (!fieldNames[0].equals(msgField.getName())) {
String msgFieldDump = visualizer.dumpMsgField(msgField);
throw new PackerRuntimeException("MsgField with name '" + fieldNames[0] + "' is not a root field. " +
"Please define the absolute path from the root field. " +
"Actual root field name: '" + msgField.getName() + "'." +
MSG_FIELD + msgFieldDump);
}
for (int i = 1; i < fieldNames.length; i++) {
String name = fieldNames[i];
if (name == null) {
throw new PackerRuntimeException("MsgField name cannot be 'null'. " +
"FieldNames: " + Arrays.toString(fieldNames));
}
try {
jumpToChild(name);
} catch (Exception e) {
throw new PackerRuntimeException("Cannot find MsgField with path '" +
Arrays.toString(fieldNames) + "'", e);
}
}
return this;
}
/**
* Call the {@link #jumpAbsolute(String...)} method.
*
* @param fieldNames the {@link MsgField#getName()}s of fields from the root of the {@link #msgField}.
* @return The current {@link ValueHolder} instance with a new place (location) of the {@link #msgValue}.
*/
public ValueHolder jumpAbsolute(List fieldNames) {
return jumpAbsolute(fieldNames.toArray(new String[0]));
}
/**
* Call the {@link #getValue(Class)} method with the type of {@link #msgValue} body value.
* @param to be used for casting the returned value
* @return the {@link MsgValue#getBodyValue()} casted to T or 'null'.
*/
@SuppressWarnings("unchecked")
public T getValue() {
Object bodyValue = this.msgValue.getBodyValue();
if (bodyValue == null) {
return null;
}
return (T) getValue(bodyValue.getClass());
}
/**
* Call the {@link #getValue(Class)} method with the type of {@link #msgValue} body value. Current positions in the
* {@link #msgField} and {@link #msgValue} object graphs remain unchanged.
*
* @param to be used for casting the returned value
* @param absolutePath the {@link MsgValue#getName()}s of fields from the root of the {@link #msgValue}.
* @return the {@link MsgValue#getBodyValue()} casted to T or 'null'.
*/
@SuppressWarnings("unchecked")
public T getValue(String... absolutePath) {
MsgField currentMsgField = this.msgField;
MsgValue currentMsgValue = this.msgValue;
jumpAbsolute(absolutePath);
Object bodyValue = this.msgValue.getBodyValue();
if (bodyValue == null) {
return null;
}
T value = (T) getValue(bodyValue.getClass());
this.msgField = currentMsgField;
this.msgValue = currentMsgValue;
return value;
}
/**
* Call the {@link #getValue(String...)} method.
* @param to be used for casting the returned value
* @param absolutePath the {@link MsgValue#getName()}s of fields from the root of the {@link #msgValue}
* @return the {@link MsgValue#getBodyValue()} casted to T or 'null'.
*/
public T getValue(List absolutePath) {
return getValue(absolutePath.toArray(new String[0]));
}
/**
* Check whether the current {@link #msgValue} contains a {@link MsgValue#getBodyValue()}. Current
* {@link #msgField} and {@link #msgValue} will not be changed.
* @param absolutePath the {@link MsgValue#getName()}s of fields from the root of the {@link #msgValue}.
* @return 'true' if the field on the path has some value.
*/
public boolean hasValue(String... absolutePath) {
if (absolutePath.length < 2) {
throw new PackerRuntimeException("Please define absolutePath as a path, for example 'ROOT', 'field1' ...");
}
MsgValue currentMsgValue = msgValue;
while (currentMsgValue.getParent() != null) {
currentMsgValue = currentMsgValue.getParent();
}
for (int i = 1; i < absolutePath.length; i++) {
List children = currentMsgValue.getChildren();
if (currentMsgValue.getBodyValue() == null && children == null) {
return false;
}
currentMsgValue = navigator.findByName(children, absolutePath[i]);
if (currentMsgValue == null ||
(currentMsgValue.getBodyValue() == null && currentMsgValue.getChildren() == null)) {
return false;
}
}
return true;
}
/**
* Call the {@link #hasValue(String...)} method.
* @param absolutePath see the {@link #hasValue(String...)} method description.
* @return See the {@link #hasValue(String...)} method description.
*/
public boolean hasValue(List absolutePath) {
return hasValue(absolutePath.toArray(new String[0]));
}
/**
* Set the {@link #msgField} and {@link #msgValue} to the current instance of the {@link ValueHolder}.
* @param msgField will be assigned to {@link #msgField}
* @param msgValue will be assigned to {@link #msgValue}
*/
public void setCurrent(MsgField msgField, MsgValue msgValue) {
this.msgField = msgField;
this.msgValue = msgValue;
}
}