package ca.uwaterloo.fydp.xcde;
import ca.uwaterloo.fydp.ossp.OSSPRundownObject;
import ca.uwaterloo.fydp.ossp.OSSPState;
import ca.uwaterloo.fydp.ossp.OSSPStimulus;
import ca.uwaterloo.fydp.ossp.std.OSSPCompoundStimulus;
import ca.uwaterloo.fydp.ossp.std.OSSPDirectoryStimulus;
import ca.uwaterloo.fydp.ossp.std.OSSPDirectoryStimulusChange;
import ca.uwaterloo.fydp.ossp.std.OSSPDirectoryStimulusCreate;
import ca.uwaterloo.fydp.ossp.std.OSSPDirectoryStimulusDelete;
public class XCDERootDirectoryUserStimulusChange implements OSSPStimulus {
public static final long serialVersionUID = 0;
public String currUserName;
public XCDERootDirectoryUserState newState;
/**
* Constructs a user change stimulus.
*
* @param _currUserName the user's current name
* @param _newState their new state
*/
public XCDERootDirectoryUserStimulusChange(String _currUserName, XCDERootDirectoryUserState _newState) {
currUserName = _currUserName;
newState = _newState;
}
public String toString() {
return "CHANGE USER " + currUserName + " TO " + newState;
}
public OSSPState apply(OSSPState curr) {
((XCDERootDirectory)curr).changeUser(currUserName, newState);
return curr.postApply(this);
}
public OSSPStimulus synchronize(OSSPStimulus other, boolean tieBreaker) {
if (other instanceof OSSPDirectoryStimulus) {
// User changes do not affect directory changes (though the reverse is
// different).
return other;
} else if (other instanceof XCDERootDirectoryUserStimulusAdd) {
// Check if the user names are the same.
XCDERootDirectoryUserStimulusAdd tmp =
(XCDERootDirectoryUserStimulusAdd)other;
if (tmp.state.userName.equals(newState.userName)) {
// An add and change message for the same user name.
// This only happens when there is a race condition for
// one client to join and another to change its name. Our
// solution is that the first one "wins", and the second
// receives a change message for its own user name, which
// tells it that there's been a conflict and it should login
// again with a different name.
// Since changing a user name and adding that user name
// cannot be simultaneously valid (see the remove code),
// tmp.state.userName should never equal newState.userName.
if (tmp.state.userName.equals(newState.userName)) {
throw new RuntimeException("Something is broken.");
}
if (tieBreaker) {
// We are on the server and were applied here first, so
// we "win". Thus, the add does nothing.
return null;
} else {
// We are on the client, so we "lose", and other gets
// applied here, which tells us to change our name. However,
// we've already applied the change here. Thus, we need to
// change the user name again according to the received add
// stimulus.
return new XCDERootDirectoryUserStimulusChange(newState.userName, tmp.state);
}
}
// Else they are different user names and thus don't affect
// each other.
return other;
} else if (other instanceof XCDERootDirectoryUserStimulusChange) {
// Check if the starting names are the same.
XCDERootDirectoryUserStimulusChange tmp =
(XCDERootDirectoryUserStimulusChange)other;
if (tmp.currUserName.equals(currUserName)) {
// Two change messages for the same user name.
// This happens when there is confusion over who
// controls a certain user name. (This should never
// happen with properly behaved clients, since any confusion
// would be resolved during connection.) We let the first
// change occur and the second be overruled. The initiator
// of the second knows to choose a new user name by virtue
// of receiving a change for itself.
if (tieBreaker) {
// We are on the server and were applied here first, so
// we "win", and other doesn't get applied.
return null;
} else {
// We are on the client, so we "lose", and other gets
// applied here. However, our own change may have
// changed the name.
return new XCDERootDirectoryUserStimulusChange(newState.userName, tmp.newState);
}
}
// Else the source names are different, but what about the target names?
// Check if the user names are the same.
if (tmp.newState.userName.equals(newState.userName)) {
// Two change messages for the same target user name.
// This only happens when there is a race condition for
// two existing clients to change to the same name. Our
// solution is that the first one "wins", and the second
// receives a change message for its own user name, which
// tells it that there's been a conflict and it should login
// again with a different name.
if (tieBreaker) {
// We are on the server and were applied here first, so
// we "win". However, we need to remove the old user name
// that the client was trying to change.
return new XCDERootDirectoryUserStimulusRemove(tmp.currUserName);
} else {
// We are on the client, so we "lose", and other gets
// applied here, which tells us to change our name. However,
// we've already done our change here. Thus we need to delete
// the old user name and change the new one.
return new OSSPCompoundStimulus(
new XCDERootDirectoryUserStimulusRemove(tmp.currUserName),
new XCDERootDirectoryUserStimulusChange(newState.userName, tmp.newState));
}
}
// Else they are different target user names and thus don't affect
// each other.
return other;
} else if (other instanceof XCDERootDirectoryUserStimulusRemove) {
XCDERootDirectoryUserStimulusRemove tmp =
(XCDERootDirectoryUserStimulusRemove)other;
// First, it if the user is being renamed, it shouldn't be possible for the
// remove to be for that name. Check this.
if ((!currUserName.equals(newState.userName)) && tmp.userName.equals(newState.userName)) {
throw new RuntimeException("Something is broken.");
}
// Now check if same source user name.
if (tmp.userName.equals(currUserName)) {
// This happens when there is confusion over which client
// controls which user name. Here, two clients think they
// control the same name. Our solution is that the first
// client to initiate a change in such a conflict situation
// gets to make it. The other guy's change is squelched and
// they're sent the change that was used. When they see
// the change, they should know that there is a conflict
// of user names and that they should change their name.
if (tieBreaker) {
return null;
} else {
// We may have changed the user name here, so we need a new remove
// for the new name.
return new XCDERootDirectoryUserStimulusRemove(newState.userName);
}
}
// Else they are different user names and thus don't affect
// each other.
return other;
} else {
return other.secondChanceSynchronize(this, tieBreaker);
}
}
private OSSPStimulus _secondChanceSynchronize(OSSPStimulus other, boolean tieBreaker, int i) {
if (newState.filePath == null) {
// If our added user is not viewing a file, then non-user stimuli
// do not affect us.
return this;
}
if (other instanceof OSSPCompoundStimulus) {
XCDERootDirectoryUserStimulusChange tmp = (XCDERootDirectoryUserStimulusChange)_secondChanceSynchronize( ((OSSPCompoundStimulus)other).getFirst(), tieBreaker, i );
if (tmp == null) {
return null;
}
return tmp._secondChanceSynchronize( ((OSSPCompoundStimulus)other).getSecond(), tieBreaker, i );
}
if ( i < newState.filePath.length ) {
// Then other must be a directory stimulus.
if (!newState.filePath[i].equals(((OSSPDirectoryStimulus)other).getAffectedElementName())) {
// Different next segment, so "other" does not affect us.
return this;
}
// Else same next segment. Check other's type.
if (other instanceof OSSPDirectoryStimulusCreate) {
// Adverse state. We couldn't have been viewing something that
// didn't exist.
throw new RuntimeException("Something is broken.");
} else if (other instanceof OSSPDirectoryStimulusDelete) {
// Someone has deleted the thing we're looking at. Change
// to not look at anything.
return new XCDERootDirectoryUserStimulusChange(currUserName, new XCDERootDirectoryUserState(newState.userName, null, null, newState.readyForBuild, newState.readyForTest));
} else {
// Else it's a change, so we continue.
other = ((OSSPDirectoryStimulusChange)other).elementChange;
return _secondChanceSynchronize(other, tieBreaker, i+1);
}
} else {
// We survived all the path segments. Thus "other" must be left as a
// stimulus for a file.
if (other instanceof XCDEDocumentAnnotationStimulus) {
// Annotation stimuli don't affect us.
return this;
} else if (other instanceof XCDEDocChange) {
// Adjust our selection.
return new XCDERootDirectoryUserStimulusChange(currUserName, new XCDERootDirectoryUserState(newState.userName, newState.filePath, ((XCDEDocChange)other).updateSelection(newState.cursor), newState.readyForBuild, newState.readyForTest));
} else {
// There are no other file stimulus types.
throw new RuntimeException("Unknown document stimulus type.");
}
}
}
public OSSPStimulus secondChanceSynchronize(OSSPStimulus other,
boolean tieBreaker) {
return _secondChanceSynchronize(other, tieBreaker, 0);
}
public OSSPRundownObject updateRundownObject(OSSPRundownObject ob, boolean isSelf) {
// If a user has been renamed, the rundown should now be for the new name. We
// don't check self, b/c we don't prevent different clients from editing
// users not logged on by them. A change to the class hierarchy should be made
// to fix this.
if (ob == null) {
return ob;
}
int i = ((XCDERootDirectoryRundownObject)ob).userNames.indexOf(currUserName);
if (i == -1) {
return ob;
}
((XCDERootDirectoryRundownObject)ob).userNames.set(i, newState.userName);
return ob;
}
}