package iamrescue.communication.messages.codec.updates; import iamrescue.communication.messages.codec.AbstractMessageCodec; import iamrescue.communication.messages.codec.BitStreamDecoder; import iamrescue.communication.messages.codec.BitStreamEncoder; import iamrescue.communication.messages.updates.EntityUpdatedMessage; import iamrescue.util.BaseConverter; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import rescuecore2.registry.Registry; import rescuecore2.worldmodel.Entity; import rescuecore2.worldmodel.EntityID; import rescuecore2.worldmodel.Property; public abstract class EntityUpdatedMessageCodec extends AbstractMessageCodec<EntityUpdatedMessage> { private static final int PROPERTY_UNCHANGED = 0; private static final int PROPERTY_UNDEFINED = 1; private static final int PROPERTY_CHANGED = 2; private static final int NUMBER_OF_PROPERTY_STATES = 3; private List<String> relevantProperties; // How many bits do we need to encode the property status private int bitsRequired; private static Log log = LogFactory.getLog(EntityUpdatedMessageCodec.class); public EntityUpdatedMessageCodec(List<String> relevantProperties) { this.relevantProperties = relevantProperties; bitsRequired = (int) Math.ceil(Math.log(Math.pow( NUMBER_OF_PROPERTY_STATES, relevantProperties.size())) / Math.log(2)); } @Override protected EntityUpdatedMessage decodeMessage(BitStreamDecoder decoder) { short timeStamp = (short) (decoder.readNumber() - Byte.MIN_VALUE); EntityUpdatedMessage message = createMessage(timeStamp); Entity object = decoder.readEntityByID(); if (object == null) { // this will happen if a message has been received about an entity // that is not known to this agent yet. Therefore, a new entity will // be created with the specified ID object = createObject(new EntityID(decoder.readNumber() - Short.MIN_VALUE)); } message.setObject(object); boolean[] propertyStatusBits = new boolean[bitsRequired]; for (int i = 0; i < bitsRequired; i++) { propertyStatusBits[i] = decoder.readBoolean(); } int[] propertyStatus = BaseConverter.convertFromBinary( propertyStatusBits, NUMBER_OF_PROPERTY_STATES); // Pad if required propertyStatus = BaseConverter.padToLength(propertyStatus, relevantProperties.size()); for (int i = 0; i < propertyStatus.length; i++) { if (propertyStatus[i] != PROPERTY_UNCHANGED) { // relevant property with index i has changed try { String propertyKey = relevantProperties.get(i); Property value; if (propertyStatus[i] == PROPERTY_CHANGED) { // if (message.providesOwnCodec(propertyKey)) { // PropertyCodec ownCodec = message // .getOwnCodec(propertyKey); // value = ownCodec.decode(object, decoder); // } else { value = decoder.readProperty(object, propertyKey); // } } else { value = Registry.getCurrentRegistry().createProperty( propertyKey.toString()); value.undefine(); } message.addUpdatedProperty(value); // object.getProperty(value.getURN()).takeValue(value); } catch (IndexOutOfBoundsException e) { log.error("Property " + i + " does not exist."); throw e; } } } return message; } @Override protected void encodeMessage(EntityUpdatedMessage message, BitStreamEncoder encoder) { encoder.appendNumber(message.getTimestamp() + Byte.MIN_VALUE); // encode id Entity object = message.getObject(); encoder.appendEntityID(object); // encode which properties have changed int[] propertyStatus = new int[relevantProperties.size()]; for (int i = 0; i < relevantProperties.size(); i++) { String property = relevantProperties.get(i); if (!message.getChangedProperties().contains(property)) { // Not included propertyStatus[i] = PROPERTY_UNCHANGED; } else { if (!message.getProperty(property).isDefined()) { // Undefined // System.out.println("Property " + // message.getObject().getProperty(property.toString()) + // " undefined."); propertyStatus[i] = PROPERTY_UNDEFINED; } else { // Defined // System.out.println("Property " + // message.getObject().getProperty(property.toString()) + // " defined."); propertyStatus[i] = PROPERTY_CHANGED; } } } boolean[] propertyAsBits = BaseConverter.convertToBinary( propertyStatus, NUMBER_OF_PROPERTY_STATES); // Pad if necessary propertyAsBits = BaseConverter .padToLength(propertyAsBits, bitsRequired); encoder.getBitStream().append(propertyAsBits); // System.out.println(Arrays.toString(propertyStatus)); for (int i = 0; i < relevantProperties.size(); i++) { String property = relevantProperties.get(i); if (propertyStatus[i] == PROPERTY_CHANGED) { try { // if (message.providesOwnCodec(property)) { // PropertyCodec ownCodec = message.getOwnCodec(property); // ownCodec.encode(object, message.getProperty(property), // encoder); // } else { encoder.appendProperty(object, message .getProperty(property)); // } } catch (Exception e) { log.error(getClass() + ": Something went wrong " + "during encoding of property " + property + " for message " + message, e); } } } } /** * The class of entity this message is about * * @return */ protected abstract Class<? extends Entity> getObjectClass(); /** * Returns a new (empty) instance of a message * * @param timeStamp * @return */ protected abstract EntityUpdatedMessage createMessage(short timeStamp); /** * Instantiates a new Entity with the correct entity id, or throws a * IllegalArgumentException if the creation of a new entity is not * supported. For example, instantiating a new instance of a Building * doesn't make sense, since all buildings are known at the start of the * simulation. However, it should be supported for messages about Civilians, * since new civilians might be detected after the start of the simulation, * and messages about (previously unknown) civilians are exchanged between * agents. * * @param id * @return */ protected abstract Entity createObject(EntityID id); }