package rescuecore2.worldmodel; import static rescuecore2.misc.EncodingTools.writeInt32; import static rescuecore2.misc.EncodingTools.writeString; import static rescuecore2.misc.EncodingTools.writeProperty; import static rescuecore2.misc.EncodingTools.readInt32; import static rescuecore2.misc.EncodingTools.readString; import static rescuecore2.misc.EncodingTools.readProperty; import java.util.Set; import java.util.HashSet; import java.util.Map; import java.util.HashMap; import java.util.Collection; import java.util.Iterator; import java.io.OutputStream; import java.io.InputStream; import java.io.IOException; import rescuecore2.misc.collections.LazyMap; import rescuecore2.log.Logger; /** This class is used for accumulating changes to entities. */ public class ChangeSet { private Map<EntityID, Map<String, Property>> changes; private Set<EntityID> deleted; private Map<EntityID, String> entityURNs; /** Create an empty ChangeSet. */ public ChangeSet() { changes = new LazyMap<EntityID, Map<String, Property>>() { @Override public Map<String, Property> createValue() { return new HashMap<String, Property>(); } }; entityURNs = new HashMap<EntityID, String>(); deleted = new HashSet<EntityID>(); } /** Copy constructor. @param other The ChangeSet to copy. */ public ChangeSet(ChangeSet other) { this(); merge(other); } /** Add a change. @param e The entity that has changed. @param p The property that has changed. */ public void addChange(Entity e, Property p) { addChange(e.getID(), e.getURN(), p); } /** Add a change. @param e The ID of the entity that has changed. @param urn The URN of the entity that has changed. @param p The property that has changed. */ public void addChange(EntityID e, String urn, Property p) { if (deleted.contains(e)) { return; } Property prop = p.copy(); changes.get(e).put(prop.getURN(), prop); entityURNs.put(e, urn); } /** Register a deleted entity. @param e The ID of the entity that has been deleted. */ public void entityDeleted(EntityID e) { deleted.add(e); changes.remove(e); } /** Get the properties that have changed for an entity. @param e The entity ID to look up. @return The set of changed properties. This may be empty but will never be null. */ public Set<Property> getChangedProperties(EntityID e) { return new HashSet<Property>(changes.get(e).values()); } /** Look up a property change for an entity by property URN. @param e The entity ID to look up. @param urn The property URN to look up. @return The changed property with the right URN, or null if the property is not found or has not changed. */ public Property getChangedProperty(EntityID e, String urn) { Map<String, Property> props = changes.get(e); if (props != null) { return props.get(urn); } return null; } /** Get the IDs of all changed entities. @return A set of IDs of changed entities. */ public Set<EntityID> getChangedEntities() { return new HashSet<EntityID>(changes.keySet()); } /** Get the IDs of all deleted entities. @return A set of IDs of deleted entities. */ public Set<EntityID> getDeletedEntities() { return new HashSet<EntityID>(deleted); } /** Get the URN of a changed entity. @param id The ID of the entity. @return The URN of the changed entity. */ public String getEntityURN(EntityID id) { return entityURNs.get(id); } /** Merge another ChangeSet into this one. @param other The other ChangeSet. */ public void merge(ChangeSet other) { for (Map.Entry<EntityID, Map<String, Property>> next : other.changes.entrySet()) { EntityID e = next.getKey(); String urn = other.getEntityURN(e); for (Property p : next.getValue().values()) { addChange(e, urn, p); } } deleted.addAll(other.deleted); } /** Add all defined properties from a collection. @param c The collection to copy changes from. */ public void addAll(Collection<? extends Entity> c) { for (Entity entity : c) { for (Property property : entity.getProperties()) { if (property.isDefined()) { addChange(entity, property); } } } } /** Write this ChangeSet to a stream. @param out The stream to write to. @throws IOException If there is a problem. */ public void write(OutputStream out) throws IOException { // Number of entity IDs writeInt32(changes.size(), out); for (Map.Entry<EntityID, Map<String, Property>> next : changes.entrySet()) { EntityID id = next.getKey(); Collection<Property> props = next.getValue().values(); // EntityID, URN, number of properties writeInt32(id.getValue(), out); writeString(getEntityURN(id), out); writeInt32(props.size(), out); for (Property prop : props) { writeProperty(prop, out); } } writeInt32(deleted.size(), out); for (EntityID next : deleted) { writeInt32(next.getValue(), out); } } /** Read this ChangeSet from a stream. @param in The stream to read from. @throws IOException If there is a problem. */ public void read(InputStream in) throws IOException { changes.clear(); deleted.clear(); int entityCount = readInt32(in); for (int i = 0; i < entityCount; ++i) { EntityID id = new EntityID(readInt32(in)); String urn = readString(in); int propCount = readInt32(in); for (int j = 0; j < propCount; ++j) { Property p = readProperty(in); if (p != null) { addChange(id, urn, p); } } } int deletedCount = readInt32(in); for (int i = 0; i < deletedCount; ++i) { EntityID id = new EntityID(readInt32(in)); deleted.add(id); } } @Override public String toString() { StringBuilder result = new StringBuilder(); result.append("ChangeSet:"); for (Map.Entry<EntityID, Map<String, Property>> next : changes.entrySet()) { result.append(" Entity "); result.append(next.getKey()); result.append(" ("); result.append(getEntityURN(next.getKey())); result.append(") ["); for (Iterator<Property> it = next.getValue().values().iterator(); it.hasNext();) { result.append(it.next()); if (it.hasNext()) { result.append(", "); } } result.append("]"); } result.append(" {Deleted "); for (Iterator<EntityID> it = deleted.iterator(); it.hasNext();) { result.append(it.next()); if (it.hasNext()) { result.append(", "); } } result.append("}"); return result.toString(); } /** Write this changeset to Logger.debug in a readable form. */ public void debug() { Logger.debug("ChangeSet"); for (Map.Entry<EntityID, Map<String, Property>> next : changes.entrySet()) { Logger.debug(" Entity " + next.getKey() + "(" + getEntityURN(next.getKey()) + ")"); for (Iterator<Property> it = next.getValue().values().iterator(); it.hasNext();) { Logger.debug(" " + it.next()); } } for (Iterator<EntityID> it = deleted.iterator(); it.hasNext();) { Logger.debug(" Deleted: " + it.next()); } } }