Revision: 3484
Author: silva.josemanuel1
Date: Fri Apr 30 15:31:53 2010
Log: Added lots of conflict detection to NetworkConflictResolver. To see
what constitutes a conflict, see the ConflictCases enum within
NetworkConflictResolver, and the checkForSimultaneousEdit. Not all the
cases have been tested.
http://code.google.com/p/power-architect/source/detail?r=3484
Modified:
/trunk/src/main/java/ca/sqlpower/architect/enterprise/NetworkConflictResolver.java
/trunk/src/main/java/ca/sqlpower/architect/swingui/PlayPen.java
/trunk/src/main/java/ca/sqlpower/architect/swingui/PlayPenContentPane.java
=======================================
---
/trunk/src/main/java/ca/sqlpower/architect/enterprise/NetworkConflictResolver.java
Wed Apr 28 15:04:19 2010
+++
/trunk/src/main/java/ca/sqlpower/architect/enterprise/NetworkConflictResolver.java
Fri Apr 30 15:31:53 2010
@@ -20,8 +20,15 @@
import java.net.URI;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.http.client.HttpClient;
@@ -35,9 +42,9 @@
import org.springframework.security.AccessDeniedException;
import ca.sqlpower.architect.ArchitectSession;
+import ca.sqlpower.architect.swingui.PlayPenComponent;
+import ca.sqlpower.architect.swingui.PlayPenContentPane;
import ca.sqlpower.dao.MessageSender;
-import ca.sqlpower.dao.PersistedObjectEntry;
-import ca.sqlpower.dao.PersistedPropertiesEntry;
import ca.sqlpower.dao.PersistedSPOProperty;
import ca.sqlpower.dao.PersistedSPObject;
import ca.sqlpower.dao.RemovedObjectEntry;
@@ -48,6 +55,7 @@
import ca.sqlpower.dao.json.SPJSONMessageDecoder;
import ca.sqlpower.dao.session.SessionPersisterSuperConverter;
import ca.sqlpower.object.SPObject;
+import ca.sqlpower.sqlobject.SQLRelationship.ColumnMapping;
import ca.sqlpower.util.SQLPowerUtils;
import ca.sqlpower.util.UserPrompter.UserPromptOptions;
import ca.sqlpower.util.UserPrompter.UserPromptResponse;
@@ -84,17 +92,13 @@
private JSONArray messageBuffer = new JSONArray();
- private List<PersistedSPObject> inboundObjectsToAdd = new
LinkedList<PersistedSPObject>();
+ private HashMap<String, PersistedSPObject> inboundObjectsToAdd = new
HashMap<String, PersistedSPObject>();
private Multimap<String, PersistedSPOProperty>
inboundPropertiesToChange = LinkedListMultimap.create();
- private List<RemovedObjectEntry> inboundObjectsToRemove = new
LinkedList<RemovedObjectEntry>();
-
- private List<PersistedSPObject> outboundObjectsToAdd = new
LinkedList<PersistedSPObject>();
+ private HashMap<String, RemovedObjectEntry> inboundObjectsToRemove =
new HashMap<String, RemovedObjectEntry>();
+
+ private Map<String, PersistedSPObject> outboundObjectsToAdd = new
LinkedHashMap<String, PersistedSPObject>();
private Multimap<String, PersistedSPOProperty>
outboundPropertiesToChange = LinkedListMultimap.create();
- private List<RemovedObjectEntry> outboundObjectsToRemove = new
LinkedList<RemovedObjectEntry>();
-
- private List<PersistedObjectEntry> outboundObjectsToAddRollbackList =
new LinkedList<PersistedObjectEntry>();
- private List<PersistedPropertiesEntry>
outboundPropertiesToChangeRollbackList = new
LinkedList<PersistedPropertiesEntry>();
- private List<RemovedObjectEntry> outboundObjectsToRemoveRollbackList =
new LinkedList<RemovedObjectEntry>();
+ private Map<String, RemovedObjectEntry> outboundObjectsToRemove = new
LinkedHashMap<String, RemovedObjectEntry>();
private List<UpdateListener> updateListeners = new
ArrayList<UpdateListener>();
@@ -172,14 +176,12 @@
return;
}
if (response.isSuccessful()) {
- // Sent json message without conflict.
+ // Sent json message without conflict.
try {
currentRevision = (new
JSONObject(response.getBody())).getInt("currentRevision");
} catch (JSONException e) {
throw new RuntimeException("Could not update current
revision" + e.getMessage());
}
- // Prepare for next send ...
- clear(reflush);
} else {
// Did not successfully post json, we must update
ourselves, and then try again if we can.
if (!reflush) {
@@ -226,23 +228,35 @@
// Try to apply update
decodeMessage(json, newRev);
// We need an additional step here for checking for
special case conflicts
- if (detectConflict()) {
- throw new RuntimeException("There is a conflict
between our state and the server's, our changes will be lost");
- } else {
+ List<ConflictMessage> conflicts = detectConflicts();
+ if (conflicts.size() == 0) {
// Try to return the persisted objects to their state
pre-update.
try {
SPSessionPersister.redoForSession(session.getWorkspace(),
- outboundObjectsToAdd,
outboundPropertiesToChange,
- outboundObjectsToRemove, converter);
+ new
LinkedList<PersistedSPObject>(outboundObjectsToAdd.values()),
+ outboundPropertiesToChange,
+ new
LinkedList<RemovedObjectEntry>(outboundObjectsToRemove.values()),
converter);
// We want to re-send our changes, but only if we
were able to restore them
flush(true);
} catch (Exception ex) {
throw new RuntimeException("Reflush failed on
rollforward", ex);
}
+ } else {
+ String message = "";
+ message += "Your changes have been discarded due to a
conflict between you and another user: \n";
+ for (ConflictMessage conflict : conflicts) {
+ message += conflict.getMessage() + "\n";
+ }
+ session.createUserPrompter(message,
+ UserPromptType.MESSAGE,
+ UserPromptOptions.OK,
+ UserPromptResponse.OK,
+ "OK", "OK").promptUser("");
}
}
} finally {
postingJSON.set(false);
+ clear(true);
}
}
@@ -261,10 +275,6 @@
outboundObjectsToAdd.clear();
outboundPropertiesToChange.clear();
outboundObjectsToRemove.clear();
-
- outboundObjectsToAddRollbackList.clear();
- outboundPropertiesToChangeRollbackList.clear();
- outboundObjectsToRemoveRollbackList.clear();
}
}
@@ -393,22 +403,13 @@
private void fillOutboundPersistedLists() {
for (PersistedSPObject obj : listener.getPersistedObjects()) {
- outboundObjectsToAdd.add(obj);
+ outboundObjectsToAdd.put(obj.getUUID(), obj);
}
for (PersistedSPOProperty prop :
listener.getPersistedProperties()) {
outboundPropertiesToChange.put(prop.getUUID(), prop);
}
for (RemovedObjectEntry rem : listener.getObjectsToRemove()) {
- outboundObjectsToRemove.add(rem);
- }
- for (PersistedObjectEntry poe : listener.getObjectsRollbackList())
{
- outboundObjectsToAddRollbackList.add(poe);
- }
- for (PersistedPropertiesEntry ppe :
listener.getPropertiesRollbackList()) {
- outboundPropertiesToChangeRollbackList.add(ppe);
- }
- for (RemovedObjectEntry roe : listener.getRemovedRollbackList()) {
- outboundObjectsToRemoveRollbackList.add(roe);
+ outboundObjectsToRemove.put(rem.getRemovedChild().getUUID(),
rem);
}
}
@@ -425,7 +426,7 @@
String uuid = obj.getString("uuid");
int index = obj.getInt("index");
- inboundObjectsToAdd.add(new
PersistedSPObject(parentUUID, type, uuid, index));
+ inboundObjectsToAdd.put(uuid, new
PersistedSPObject(parentUUID, type, uuid, index));
} else if
(obj.getString("method").equals("persistProperty")) {
@@ -453,7 +454,7 @@
String uuid = obj.getString("uuid");
SPObject objectToRemove =
SQLPowerUtils.findByUuid(session.getWorkspace(), uuid, SPObject.class);
- inboundObjectsToRemove.add(new
RemovedObjectEntry(parentUUID, objectToRemove,
+ inboundObjectsToRemove.put(uuid, new
RemovedObjectEntry(parentUUID, objectToRemove,
objectToRemove.getParent().getChildren().indexOf(objectToRemove)));
}
}
@@ -462,23 +463,448 @@
}
}
- private boolean detectConflict() {
- // XXX : Special cases are to be looked for here.
- if (checkForSimultaneousEdit()) return true;
- // No special cases found.
- return false;
+ private List<ConflictMessage> detectConflicts() {
+ List<ConflictMessage> conflicts = checkForSimultaneousEdit();
+ // ----- Special cases -----
+ allowSimultaneousAdditionsUnderDB(conflicts);
+ disallowColumnMappingsPointingToSameColumn(conflicts);
+ return conflicts;
+ }
+
+ /**
+ * This method will make sure that column mappings of the same
relationship
+ * do not point to the same column of a table, since this is illegal.
+ */
+ private void
disallowColumnMappingsPointingToSameColumn(List<ConflictMessage> conflicts)
{
+ /**
+ * Stores the uuids of columns that are being pointed to
+ * by column mappings. Will store them like so:
relationshipId:columnId
+ */
+ Set<String> duplicates =
getColumnMappingChanges(outboundPropertiesToChange);
+
duplicates.retainAll(getColumnMappingChanges(inboundPropertiesToChange));
+ for (String duplicate : duplicates) {
+ String[] ids = duplicate.split("\\:");
+ String relationshipId = ids[0];
+ String columnId = ids[1];
+ String relationshipName =
session.getWorkspace().getObjectInTree(relationshipId).getName();
+ String columnName =
session.getWorkspace().getObjectInTree(columnId).getName();
+ String message = "More than one column mapping of
relationship " +
+ relationshipName + " points to the column "
+ columnName;
+ conflicts.add(new ConflictMessage(
+ message, ConflictCase.SPECIAL_CASE, relationshipId,
columnId));
+ }
+
}
- private boolean checkForSimultaneousEdit() {
- String targetDBuuid = session.getWorkspace().getUUID();
- for (PersistedSPObject inSpo : inboundObjectsToAdd) {
- for (PersistedSPObject outSpo : outboundObjectsToAdd) {
- if (inSpo.getParentUUID().equals(outSpo.getParentUUID())
&& !inSpo.equals(targetDBuuid)) {
- return true;
- }
+ /**
+ * Returns a set of strings indicating which columns were pointed to
+ * as a result of column mapping property changes, found in the given
properties map.
+ * The format of the strings are {relationshipId}:{columnId}
+ */
+ private Set<String> getColumnMappingChanges(Multimap<String,
PersistedSPOProperty> properties) {
+ Set<String> changes = new HashSet<String>();
+ for (String uuid : properties.keySet()) {
+ Class type;
+ String parentId;
+ SPObject spo = session.getWorkspace().getObjectInTree(uuid);
+ PersistedSPObject o = outboundObjectsToAdd.get(uuid);
+ try {
+ if (spo != null) {
+ type = spo.getClass();
+ parentId = spo.getParent().getUUID();
+ } else if (o != null) {
+ type = Class.forName(o.getType());
+ parentId = o.getParentUUID();
+ } else {
+ continue;
+ }
+ if (ColumnMapping.class.isAssignableFrom(type)) {
+ for (PersistedSPOProperty p : properties.get(uuid)) {
+ if (p.getDataType() == DataType.REFERENCE) {
+ changes.add(parentId + ":" + (String)
p.getNewValue());
+ }
+ }
+ }
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
}
}
- return false;
+ return changes;
+ }
+
+ /**
+ * This method will iterate over the given conflicts looking for those
of
+ * type simulatenous addition. If this conflict was due to a
simulatenous
+ * addition under the target database or the play pen content pane,
+ * the conflict is removed from the list and the index of the call is
fixed.
+ */
+ private void allowSimultaneousAdditionsUnderDB(List<ConflictMessage>
conflicts) {
+ Iterator<ConflictMessage> iterator = conflicts.iterator();
+ List<PersistedSPObject> indexUpdates = new
LinkedList<PersistedSPObject>();
+ while (iterator.hasNext()) {
+ ConflictMessage conflict = iterator.next();
+ if (conflict.getConflictCase() ==
ConflictCase.SIMULTANEOUS_ADDITION) {
+ PersistedSPObject o =
outboundObjectsToAdd.get(conflict.getObjectId(0));
+ SPObject parent =
session.getWorkspace().getObjectInTree(o.getParentUUID());
+ if (parent == session.getTargetDatabase()) {
+ iterator.remove();
+ int size =
session.getTargetDatabase().getChildren().size();
+ indexUpdates.add(new PersistedSPObject(
+ o.getParentUUID(), o.getType(), o.getUUID(),
size));
+ } else if (parent ==
session.getWorkspace().getPlayPenContentPane()) {
+ iterator.remove();
+ PlayPenContentPane cp =
session.getWorkspace().getPlayPenContentPane();
+ try {
+ Class<PlayPenComponent> type =
(Class<PlayPenComponent>) Class.forName(o.getType());
+ int newIndex = -1;
+ if
(PlayPenContentPane.isDependentComponentType(type)) {
+ if (o.getIndex() <
cp.getFirstDependentComponentIndex()) {
+ newIndex = cp.getChildren().size();
+ }
+ } else {
+ if (o.getIndex() >=
cp.getFirstDependentComponentIndex()
+ && o.getIndex() > 0) {
+ newIndex =
cp.getFirstDependentComponentIndex() - 1;
+ }
+ }
+ if (newIndex > -1) {
+ indexUpdates.add(new PersistedSPObject(
+ o.getParentUUID(), o.getType(),
o.getUUID(), newIndex));
+ }
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ for (PersistedSPObject o : indexUpdates) {
+ outboundObjectsToAdd.put(o.getUUID(), o);
+ }
+ }
+
+ /**
+ * Goes through all the inbound and outbound change lists and
+ * determines whether the outbound changes should be allowed to
continue.
+ * The reasons to prevent the outbound changes are usually cases where
+ * as a result of the incoming change, the outbound change would not be
+ * possible through the UI anymore, and/or are impossible in such a
state.
+ *
+ * See ConflictCase for all the cases that are looked for in this
method.
+ * A Google Docs spreadsheet called Conflict rules has been shared
+ * with the psc group. For more information, see that.
+ */
+ private List<ConflictMessage> checkForSimultaneousEdit() {
+
+ List<ConflictMessage> conflicts = new
LinkedList<ConflictMessage>();
+
+ Set<String> inboundAddedObjectParents = new HashSet<String>();
+ Set<String> inboundRemovedObjectParents = new HashSet<String>();
+
+ Set<String> inboundChangedObjects = new HashSet<String>();
+ HashMap<String, String> inboundCreatedDependencies = new
HashMap<String, String>();
+
+ Set<String> duplicateMoves = new HashSet<String>();
+
+
+ // ----- Populate the inbound sets / maps -----
+
+ for (String uuid : inboundPropertiesToChange.keys()) {
+ inboundChangedObjects.add(uuid);
+ for (PersistedSPOProperty p :
inboundPropertiesToChange.get(uuid)) {
+ if (p.getDataType() == DataType.REFERENCE) {
+ inboundCreatedDependencies.put((String)
p.getNewValue(), p.getUUID());
+ }
+ }
+ }
+
+ for (PersistedSPObject o : inboundObjectsToAdd.values()) {
+ inboundAddedObjectParents.add(o.getParentUUID());
+ }
+
+ for (RemovedObjectEntry o : inboundObjectsToRemove.values()) {
+ inboundRemovedObjectParents.add(o.getParentUUID());
+ }
+
+ // ----- Iterate through outbound additions -----
+
+ Set<String> checkedIfCanAddToTree = new HashSet<String>();
+ Iterator<PersistedSPObject> addedObjects =
outboundObjectsToAdd.values().iterator();
+ while (addedObjects.hasNext()) {
+ PersistedSPObject o = addedObjects.next();
+
+ // Can't add object to a parent that already had a child added
or removed.
+ // This will also include incoming and/or outgoing moves,
which are conflicts too.
+ if (inboundAddedObjectParents.contains(o.getParentUUID()) ||
+
inboundRemovedObjectParents.contains(o.getParentUUID())) {
+ conflicts.add(new
ConflictMessage(ConflictCase.SIMULTANEOUS_ADDITION,
+ o.getUUID(), getPersistedObjectName(o)));
+ }
+
+ // Can't add an object if the direct parent was changed.
+ if (inboundChangedObjects.contains(o.getParentUUID())) {
+ conflicts.add(new
ConflictMessage(ConflictCase.ADDITION_UNDER_CHANGE,
+ o.getUUID(), getPersistedObjectName(o),
+ o.getParentUUID(),
session.getWorkspace().getObjectInTree(o.getParentUUID()).getName()));
+ }
+
+ // Make sure we are not adding an object that had an ancestor
removed.
+ // First iterate up ancestors that are being added in the same
transaction.
+ PersistedSPObject highestAddition = o;
+ while
(outboundObjectsToAdd.containsKey(highestAddition.getParentUUID()) &&
+ !checkedIfCanAddToTree.contains(highestAddition.getParentUUID()))
{
+ checkedIfCanAddToTree.add(highestAddition.getUUID());
+ highestAddition =
outboundObjectsToAdd.get(highestAddition.getParentUUID());
+ }
+ checkedIfCanAddToTree.add(highestAddition.getUUID());
+ if (checkedIfCanAddToTree.add(highestAddition.getParentUUID())
&&
+
session.getWorkspace().getObjectInTree(highestAddition.getParentUUID()) ==
null) {
+ conflicts.add(new
ConflictMessage(ConflictCase.ADDITION_UNDER_REMOVAL,
+ highestAddition.getUUID(),
getPersistedObjectName(highestAddition)));
+ }
+
+ // Check if both clients are adding the same object.
+ // It could mean they both undid a deletion of this object,
+ // or are both trying to move the same object.
+ // If they are identical, remove the outbound add from this
list.
+ // If it was a move and has a corresponding remove call, that
+ // must be taken care of in the following outbound removals
loop.
+ if (inboundObjectsToAdd.containsKey(o.getUUID())) {
+ if (inboundObjectsToAdd.get(o.getUUID()).equals(o)) {
+ addedObjects.remove();
+ outboundPropertiesToChange.removeAll(o.getUUID());
+ duplicateMoves.add(o.getUUID());
+ } else {
+ conflicts.add(new
ConflictMessage(ConflictCase.DIFFERENT_MOVE,
+ o.getUUID(), getPersistedObjectName(o)));
+ }
+ }
+ }
+
+
+ // ----- Iterate through outbound removals -----
+
+ Iterator<RemovedObjectEntry> removedObjects =
outboundObjectsToRemove.values().iterator();
+ while (removedObjects.hasNext()) {
+ RemovedObjectEntry object = removedObjects.next();
+ final String uuid = object.getRemovedChild().getUUID();
+
+ // Check if the object the outbound client is trying to remove
does not exist.
+ SPObject removedObject =
session.getWorkspace().getObjectInTree(uuid);
+ if (removedObject == null) {
+ // Check if this remove has a corresponding add, meaning
it is a move.
+ // The incoming remove will override the outgoing move.
+ if (outboundObjectsToAdd.containsKey(uuid)) {
+ conflicts.add(new
ConflictMessage(ConflictCase.MOVE_OF_REMOVED,
+ object.getRemovedChild().getUUID(),
object.getRemovedChild().getName()));
+ } else {
+ // Both clients removed the same object, either
directly or indirectly.
+ removedObjects.remove();
+ }
+ } else if (inboundCreatedDependencies.containsKey(uuid)) {
+ // Can't remove an object that was just made a dependency
+ String uuidOfDependent =
inboundCreatedDependencies.get(uuid);
+ conflicts.add(new
ConflictMessage(ConflictCase.REMOVAL_OF_DEPENDENCY,
+ uuid, removedObject.getName(),
+ uuidOfDependent,
session.getWorkspace().getObjectInTree(uuidOfDependent).getName()));
+ } else if (duplicateMoves.contains(uuid)) {
+ removedObjects.remove();
+ }
+
+ }
+
+
+ // ----- Iterate through outbound properties -----
+
+ for (String uuid : outboundPropertiesToChange.keys()) {
+ SPObject changedObject =
session.getWorkspace().getObjectInTree(uuid);
+
+ // If this object is being newly added, the rest of the loop
body does not matter.
+ if (outboundObjectsToAdd.containsKey(uuid)) continue;
+
+ // Cannot change a property on an object that no longer exists
(due to inbound removal).
+ if (changedObject == null) {
+ conflicts.add(new
ConflictMessage(ConflictCase.CHANGE_OF_REMOVED, uuid, uuid));
+ continue;
+ }
+
+ // Cannot change the property of an object whose direct parent
was also changed.
+ if (changedObject.getParent() != null &&
+
inboundChangedObjects.contains(changedObject.getParent().getUUID())) {
+ conflicts.add(new
ConflictMessage(ConflictCase.CHANGE_UNDER_CHANGE,
+ uuid, changedObject.getName(),
+ changedObject.getParent().getUUID(),
changedObject.getParent().getName()));
+ }
+
+ // You cannot change the property of an object that had a
property already changed,
+ // unless any and all property changes are identical, in which
case the duplicate
+ // property changes will be removed from the outgoing list.
+
+ if (inboundChangedObjects.contains(uuid)) {
+ ConflictMessage message = new
ConflictMessage(ConflictCase.SIMULTANEOUS_OBJECT_CHANGE,
+ uuid,
session.getWorkspace().getObjectInTree(uuid).getName());
+
+ HashMap<String, Object> inboundPropertiesMap =
+ new HashMap<String, Object>();
+ for (PersistedSPOProperty p :
inboundPropertiesToChange.get(uuid)) {
+ inboundPropertiesMap.put(p.getPropertyName(),
p.getNewValue());
+ }
+
+ Iterator<PersistedSPOProperty> properties =
outboundPropertiesToChange.get(uuid).iterator();
+ while (properties.hasNext()) {
+ PersistedSPOProperty p = properties.next();
+ // Check if there is a corresponding inbound property.
+ // If not, this is a conflict since there are
non-identical properties.
+ if
(inboundPropertiesMap.containsKey(p.getPropertyName())) {
+ if
(inboundPropertiesMap.get(p.getPropertyName()).equals(p.getNewValue())) {
+ properties.remove();
+ } else {
+ conflicts.add(message);
+ break;
+ }
+ } else {
+ conflicts.add(message);
+ break;
+ }
+ }
+ }
+
+ // Cannot change the property of a parent whose direct child
was either:
+ for (SPObject child : changedObject.getChildren()) {
+ // also changed
+ if (inboundChangedObjects.contains(child.getUUID())) {
+ conflicts.add(new
ConflictMessage(ConflictCase.CHANGE_UNDER_CHANGE,
+ uuid, changedObject.getName(),
+ child.getUUID(), child.getName()));
+ }
+
+ // or just added (moved is okay, though).
+ if (inboundObjectsToAdd.containsKey(child.getUUID()) &&
+ !inboundObjectsToRemove.containsKey(child.getUUID())){
+ conflicts.add(new
ConflictMessage(ConflictCase.CHANGE_AFTER_ADDITION,
+ uuid, changedObject.getName(),
+ child.getUUID(), child.getName()));
+ }
+ }
+ }
+ return conflicts;
+ }
+
+ private String getPersistedObjectName(PersistedSPObject o) {
+ for (PersistedSPOProperty p :
outboundPropertiesToChange.get(o.getUUID())) {
+ if (p.getPropertyName().equals("name")) {
+ return (String) p.getNewValue();
+ }
+ }
+ throw new IllegalArgumentException("Given persisted object has no
corresponding name property!");
+ }
+
+ /**
+ * A class that will take a ConflictCase and parameters insert
+ * them into the ConflictCase's message.
+ */
+ private class ConflictMessage {
+
+ private final ConflictCase conflict;
+ private final String message;
+ private final List<String> objectIds = new LinkedList<String>();
+ private final List<String> objectNames = new LinkedList<String>();
+
+ /**
+ * Create a conflict message using the ConflictCase's message
+ * and String.format() to put the given arguments in
+ * @param conflict
+ * @param uuidsAndNames A list of the relevant object ids and
names, in pairs.
+ * ie: "id1", "table1", "id2", "table2"
+ */
+ public ConflictMessage(ConflictCase conflict, String ...
uuidsAndNames) {
+ this.conflict = conflict;
+ for (int i = 0; i < uuidsAndNames.length; i += 2) {
+ objectIds.add(uuidsAndNames[i]);
+ objectNames.add(uuidsAndNames[i+1]);
+ }
+ if (objectIds.size() != conflict.numArgs()) {
+ throw new IllegalArgumentException(
+ "Number of arguments passed in does not match number
requested by conflict type");
+ }
+ String b = String.format("%s %s", "test", "not");
+ try {
+ message = String.format(conflict.message,
objectNames.toArray());
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ /**
+ * This constructor is used for custom messages.
+ * Will not call String.format().
+ * @param message
+ * @param conflict
+ * @param uuids
+ */
+ public ConflictMessage(String message, ConflictCase conflict,
String ... uuids) {
+ this.message = message;
+ this.conflict = conflict;
+ objectIds.addAll(Arrays.asList(uuids));
+ }
+
+ public String getObjectId(int index) {
+ return objectIds.get(index);
+ }
+
+ public ConflictCase getConflictCase() {
+ return conflict;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public String toString() {
+ return message;
+ }
+
+ }
+
+ /**
+ * Defines conflict cases as well as messages for each.
+ */
+ private static enum ConflictCase {
+ NO_CONFLICT ("There were no conflicts"),
+
+ ADDITION_UNDER_REMOVAL ("The %s you tried adding could not be
added because its ancestor was removed"),
+
+ MOVE_OF_REMOVED ("Could not move %s because it was removed"),
+
+ CHANGE_OF_REMOVED ("Could not change %s because it was removed"),
+
+ SIMULTANEOUS_ADDITION ("Could not add %s because a sibling was
added/removed"),
+
+ ADDITION_UNDER_CHANGE ("Could not add %s because its parent %s was
modified"),
+
+ CHANGE_AFTER_ADDITION ("Could not change %s because child %s was
added"),
+
+ CHANGE_UNDER_CHANGE ("Could not change %s because its
parent/child %s was modified"),
+
+ REMOVAL_OF_DEPENDENCY ("Could not remove %s because another
object %s is now dependent on it"),
+
+ SIMULTANEOUS_OBJECT_CHANGE ("Could not change %s because it was
changed by another user"),
+
+ DIFFERENT_MOVE ("Could not move %s because it was moved somewhere
else"),
+
+ SPECIAL_CASE ("");
+
+ private final String message;
+
+ ConflictCase(String s) {
+ message = s;
+ }
+
+ /**
+ * Returns the number of parameters the enum expects in its
message.
+ */
+ public int numArgs() {
+ return message.length() - message.replace("%s", "1").length();
+ }
}
/**
=======================================
--- /trunk/src/main/java/ca/sqlpower/architect/swingui/PlayPen.java Thu Apr
22 14:28:36 2010
+++ /trunk/src/main/java/ca/sqlpower/architect/swingui/PlayPen.java Fri Apr
30 15:31:53 2010
@@ -1711,7 +1711,7 @@
PlayPenComponent ppc =
removedComponents.get(child.getUUID());
if (ppc != null) {
- if
(contentPane.isDependentComponentType(ppc.getClass())) {
+ if
(PlayPenContentPane.isDependentComponentType(ppc.getClass())) {
contentPane.addChild(ppc,
contentPane.getFirstDependentComponentIndex());
} else {
contentPane.addChild(ppc, 0);
=======================================
---
/trunk/src/main/java/ca/sqlpower/architect/swingui/PlayPenContentPane.java
Thu Apr 29 08:28:11 2010
+++
/trunk/src/main/java/ca/sqlpower/architect/swingui/PlayPenContentPane.java
Fri Apr 30 15:31:53 2010
@@ -204,7 +204,7 @@
}
@NonBound
- public boolean isDependentComponentType(Class<? extends
PlayPenComponent> componentType) {
+ public static boolean isDependentComponentType(Class<? extends
PlayPenComponent> componentType) {
for (Class<? extends PlayPenComponent> dependentType :
dependentComponentTypes) {
if (dependentType.isAssignableFrom(componentType)) return true;
}