/**
* Copyright (c) 2013, Will Szumski
*
* This file is part of formicidae.
*
* formicidae is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* formicidae is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with formicidae. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*
*/
package org.cowboycoders.ant.messages;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.cowboycoders.ant.messages.Constants.DataElement;
import org.cowboycoders.ant.utils.BitUtils;
import org.cowboycoders.ant.utils.DataElementUtils;
import org.cowboycoders.ant.utils.IntUtils;
import org.cowboycoders.ant.utils.ValidationUtils;
/**
* Standard Decorator with no added functionality
*
* @author will
*
*/
public abstract class StandardMessage
implements MessageDecorator, Messageable {
/**
* The message being decorated
*/
private Message message;
/**
* original mesasgeId - used to check this doesn't change during a decode
*/
private final MessageId id;
/**
* If true, will validate that the message length is at least as long as
* {@code messageElements#size()}
*/
private boolean allElementsMustBePresent = false;
/**
* holds the total length (in bytes) of all elements in {@code messageElements}
*/
private int totalElementLength = 0;
/**
* Stores the message elements in the order they appear in the message
*/
private DataElement [] messageElements;
/**
* @return the messageElements
*/
private DataElement[] getMessageElements() {
return messageElements;
}
/**
* @param messageElements the messageElements to set
*/
private void setMessageElements(DataElement[] messageElements) {
this.messageElements = messageElements;
}
protected StandardMessage(MessageId id,
DataElement[] messageElements) {
this(null, id, messageElements);
}
/**
* Decorates a copy of the message
* @param message the <code>Message</code> to decorate
* @param id the <code>MessageId</code> id to use
* @param messageElements list of elements
* @throws FatalMessageException if there is an error building message
*/
protected StandardMessage(Message message, MessageId id,
ArrayList<DataElement> messageElements) {
this(message,id,messageElements.toArray(new DataElement[0]));
}
/**
* Decorates a copy of the message
* @param message the <code>Message</code> to decorate
* @param id the <code>MessageId</code> id to use
* @param messageElements list of elements
* @throws FatalMessageException if there is an error building message
*/
protected StandardMessage(Message message, MessageId id,
DataElement [] messageElements) {
this.id = id;
ArrayList<Byte> payload = new ArrayList<Byte>();
if (message == null) {
message = new Message();
}
if (messageElements == null) {
throw new FatalMessageException("StandardMessage: messageElements cannot be null");
}
this.message = message.clone();
this.message.reset();
this.message.setId(id);
this.messageElements = messageElements;
for (DataElement element : messageElements) {
int elementLength = element.getLength();
totalElementLength += elementLength;
for (int i= 0 ; i < elementLength ; i++) {
payload.add((byte) 0);
}
}
try {
this.message.setStandardPayload(payload);
} catch (ValidationException e) {
throw new FatalMessageException("Error setting payload", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public final MessageId getId() {
return message.getId();
}
/**
* {@inheritDoc}
*/
protected
final void setId(MessageId id) {
message.setId(id);
}
/**
* {@inheritDoc}
*/
@Override
public final byte getPayloadSize() {
return message.getPayloadSize();
}
/**
* {@inheritDoc}
*/
@Override
public final byte[] encode() {
return message.encode();
}
/**
* {@inheritDoc}
*/
@Override
public final List<Byte> getPayloadToSend() {
return message.getPayloadToSend();
}
/**
* {@inheritDoc}
*/
protected
final void decode(byte[] buffer, boolean noChecks) throws MessageException {
message.decode(buffer, noChecks);
if (!noChecks) {
if (message.getId() != this.id) {
throw new MessageException("Mesage Id does not match that expected for" +
" " + this.getClass());
}
if (this.allElementsMustBePresent && getStandardPayload().size() < totalElementLength) {
throw new MessageException("Insuffucient data for" +
" " + this.getClass());
}
validate();
}
}
/**
* {@inheritDoc}
*/
@Override
public
final void decode(byte[] buffer) throws MessageException {
decode(buffer,false);
}
/**
* This method should validate the payload,
* so that calls to the getters will not
* throw exceptions. Throws a {@code MessagException} if invalid.
*
* Checking the messageId is equal to be that passed into
* the constructor is already taken care of.
*
* setAllElementsMustBePresent() can be used to add additional
* message length validation. If set, the message length must
* be equal to the length of the messageElements passed into
* constructor.
*
* Called after decode.
*
* @throws MessageException when not valide
*/
public abstract void validate() throws MessageException;
/**
* {@inheritDoc}
*/
@Override
public final Message getBackendMessage() {
Message message = this.message;
return message;
}
@Override
public final ArrayList<Byte> getStandardPayload() {
return message.getStandardPayload();
}
@Override
public final void setStandardPayload(ArrayList<Byte> payload)
throws ValidationException {
message.setStandardPayload(payload);
}
/**
* Sets the value of a DataElement in a given payload
* @param element to set
* @param value to set
* @param skip the number of identical elements to skip before reaching desired element
* @return true on success, else false
*/
protected boolean setDataElement(DataElement element, Integer value, int skip) {
ArrayList<Byte> payload = getStandardPayload();
boolean completed = false;
completed = DataElementUtils.setDataElement(payload,messageElements,element,value,0,skip);
if (completed) {
try {
setStandardPayload(payload);
} catch (ValidationException e) {
throw new FatalMessageException("Cannot set payload");
}
}
return completed;
}
protected boolean setDataElement(DataElement element, Integer value) {
return setDataElement(element,value,0);
}
/**
* Gets the value of a DataElement in a given payload
* @param element to get data for
* @param skip the number of identical elements to skip before returning
* @return the data associated with the element
*/
protected Integer getDataElement(DataElement element,int skip) {
Integer rtn = null;
ArrayList<Byte> payload = getStandardPayload();
int offset = 0;
rtn = DataElementUtils.getDataElement(payload, messageElements, element, offset,skip);
return rtn;
}
/**
* @param element to get
* @return the data element
* See {@code getDataElement(element,skip)}
*/
protected Integer getDataElement(DataElement element) {
return getDataElement(element,0);
}
/**
* Appends A DataElement to messageElements, the field that represents
* the current message structure
* @param element the element to append
*/
protected void addOptionalDataElement(DataElement element) {
messageElements = getMessageElements();
DataElement[] newElements = Arrays.copyOf(messageElements,
messageElements.length + 1);
newElements[messageElements.length] = element;
setMessageElements(newElements);
ArrayList<Byte> payload = getStandardPayload();
for (int i= 0 ; i < element.getLength() ; i++) {
payload.add((byte) 0);
}
try {
this.message.setStandardPayload(payload);
} catch (ValidationException e) {
throw new FatalMessageException("Error setting payload", e);
}
}
/**
* Sets individual bits of given {@code DataElement} specified
* using a mask and value. The value's left most bit is aligned
* with the left most bit of the mask. Values must be positive,
* with a value no greater that the maximum number that can be
* represented by within the masked bits.
*
* The mask's bits must be contiguous, or the behaviour
* is undefined.
*
* @param element the whole element to apply the mask to
* @param value the value to set in the mask bits
* @param mask only bits marked in mask are changed
*/
protected void setPartialDataElement(DataElement element, int value, int mask) {
int wholeElement = getDataElement(element);
try {
wholeElement = IntUtils.setMaskedBits(wholeElement,mask ,value);
} catch (IllegalArgumentException e) {
throw new FatalMessageException(e);
}
setDataElement(element, wholeElement);
}
/**
* Convenience method to perform a max-min validation before setting
* @param element TODO: document this
* @param value TODO: document this
* @param skip the number of identical elements to skip
* @throws ValidationException TODO : document this
*/
protected void setAndValidateDataElement(DataElement element, int value, int skip)
throws ValidationException {
Integer maxValue = element.getMaxValue();
Integer minValue = element.getMinValue();
if (maxValue == null) {
maxValue = Integer.MAX_VALUE;
}
if (minValue == null) {
minValue = 0;
}
ValidationUtils.maxMinValidator(minValue, maxValue, value,
MessageExceptionFactory.createMaxMinExceptionProducable(element.toString())
);
setDataElement(element,value,skip);
}
/**
* Convenience method to perform a max-min validation before setting
* @param element TODO: document this
* @param value TODO: document this
* @throws ValidationException TODO: document this
*/
protected void setAndValidateDataElement(DataElement element, int value) throws ValidationException {
setAndValidateDataElement(element,value,0);
}
/**
* {@inheritDoc}
*/
public byte [] toArray() {
return message.toArray();
}
/**
* @param flag true to set, false to clear
*/
protected void setAllElementsMustBePresent(boolean flag) {
this.allElementsMustBePresent = flag;
}
}