Using TextModel as the Saved Object


 class TextModel { 
private ArrayList lines;
private int selectionStart;
private TextModel savedModel;
public TextModel() {
lines = new ArrayList();
}
public TextModel(TextModel m) {
lines = m.Lines;
selectionStart = m.SelectionStart;
}
...
public void Snapshot() {
savedModel = new TextModel(this);
}
public void Restore() {
lines = savedModel.Lines;
selectionStart = savedModel.SelectionStart;
}

The test still runs. Let s extend the test to require us to build the stack:

 [Test] public void Snapshot() { 
model.SetLines(new String[] { "before", "snapshot" });
model.SelectionStart = 8; // after before ;->
AssertEquals("before", model.Lines[0]);
model.Snapshot();
model.SetLines(new String[] {"a", "new", "contents", "list"} );
model.SelectionStart = 4; // after n in new
AssertEquals("contents", model.Lines[2]);
model.Snapshot(); model.SetLines(new String[] { "here", "is", "yet", "another", "version" } );
model.SelectionStart = 7;
// before is
AssertEquals("yet", model.Lines[2]);
model.Restore();
AssertEquals("contents", model.Lines[2]);
model.Restore();
AssertEquals("before", model.Lines[0]);
AssertEquals(8, model.SelectionStart);
}

As we expect, this test doesn t run. I ll change TextModel to have an ArrayList of TextModels as its stack of saved versions:

 class TextModel { 
private ArrayList lines;
private int selectionStart;
private ArrayList savedModels;
private ArrayList SavedModels {
get {
if(savedModels == null)
savedModels = new ArrayList();
return savedModels;
}
}

public void Snapshot() {
SavedModels.Add(new TextModel(this));
}
public void Restore() {
TextModel savedModel = RemoveLastModel();
lines = savedModel.Lines;
selectionStart = savedModel.SelectionStart;
}
private TextModel RemoveLastModel() {
int last = SavedModels.Count-1; TextModel lastModel = (TextModel) SavedModels[last]; SavedModels.RemoveAt(last);
return lastModel;
}

The only tricky bits here are that we did a lazy init on savedModels, covering it with a Property that initializes the list if it s empty. Second, we had to do a little messing around to remove the last item in the list and return it, because ArrayList, in its wisdom, doesn t seem to have a method that does that. I wonder if there s a Stack object in Microsoft .NET. I ll look ”and it turns out that there is. How nice! I wish I had known that before we used the ArrayList. It would have saved literally minutes. We ll use that instead. The tests are already running, but the change is worth doing, because it will simplify the code substantially.

 class TextModel { 
private ArrayList lines;
private int selectionStart;
private Stack savedModels;
private Stack SavedModels {
get {
if(savedModels == null)
savedModels = new Stack();
return savedModels;
}
}
public void Snapshot() {
SavedModels.Push(new TextModel(this));
}
public void Restore() {
TextModel savedModel = (TextModel) SavedModels.Pop();
lines = savedModel.Lines;
selectionStart = savedModel.SelectionStart;
}
// private TextModel RemoveLastModel() {
// int last = SavedModels.Count-1;
// TextModel lastModel = (TextModel) SavedModels[last];
// SavedModels.RemoveAt(last);
// return lastModel;
// }

The commented method, of course, is to be deleted. Our tests still run!

It s about time to step back and think about what we ve done. First, though, I suspect there s a bug. The TextModel constructor from another TextModel looks like this:

 public TextModel(TextModel m) { 
lines = m.Lines;
selectionStart = m.SelectionStart;
}

I m pretty concerned that manipulations to the lines of one TextModel will affect the lines of another one that s associated with it, because we have the same lines object in both TextModels. I ll write a test to demonstrate the bug and then fix it if in fact it is a bug:

 [Test] public void SnapshotLinesIdentity() { 
model.SetLines(new String[] { "before", "snapshot" });
model.SelectionStart = 6; // after before ;->
AssertEquals("snapshot", model.Lines[1]);
model.Snapshot();
model.InsertTags(TextModel.Tags.Pre);
AssertEquals("<pre></pre>", model.Lines[1]);
model.Restore();
AssertEquals("snapshot", model.Lines[1]);
}

I expect this to fail, because the stacked TextModel has the same lines ArrayList as the original. Let s find out...and in fact the test fails, expecting snapshot but getting the pre line instead. Here s the fix:

 public TextModel(TextModel m) { 
lines = new ArrayList(m.Lines);
selectionStart = m.SelectionStart;
}

This gives our stacked TextModel its own lines array so that changes to the original s ArrayList don t bother us. I m not sure yet whether this could affect us in the final version of the stacking code, but having noticed the problem I decided it was best to fix it.

start sidebar
Lesson: Let s Reflect

We have modified the TextModel to be able to save and restore its contents, in a push-down stack style. This is good ” certainly we can back the contents up to any point where the TextModel gets involved ”and we can get it involved on every keystroke if we choose to. There is, of course, some more work to do to make this capability provide Undo capability, and there are serious questions about performance.

As it stands, the Snapshot() method makes a copy of the lines ArrayList. That could get large: some of these chapters are many lines long. And I suspect that every time we load the TextModel from the TextBox, we get all new instances of String for each of the lines, even if they are equal to the lines that we got last time. Perhaps we ll write a test to verify that if it seems important. Even the lines ArrayList is large enough, however, to be concerned about memory usage. Each String in the ArrayList probably takes up a dozen bytes of overhead, and the ArrayList cells themselves must have a similar size. A thousand-line Snapshot could include 24,000 or more bytes in addition to the text. (On the other hand, this is still a small proportion of the total size of the text, which would be perhaps 50 times larger.) Suffice to say we can foresee a memory problem.

Before we go further, we should consider again whether this is fatal to our implementation. I believe that it isn t. Let s consider what happens to the text in the lines ArrayList as the user types. For compactness, I ll show lines separated by slash characters , and I ll indicate the caret by a vertical bar. Here s a little editing sequence, as the user types on line 2 (the third line) of the text:

end sidebar
 
 abc/def/ghi/uvw/xyz 
abc/def/ghij/uvw/xyz
abc/def/ghijk/uvw/xyz
abc/def/ghijkl/uvw/xyz

See what s happening? As the user types, most of the lines in the text buffer are equal from one keystroke to the next . Only a line or two changes at any given time, and even if there s a paste operation, usually only a few lines change. We should be able to take advantage of this to reduce the storage required for a Snapshot.

Having decided that we are not yet doomed, my plan is to continue on this path . The next thing to try might be a spike to make the XML Notepad do a Snapshot() on every character typed, just to see what happens.




Extreme Programming Adventures in C#
Javaв„ў EE 5 Tutorial, The (3rd Edition)
ISBN: 735619492
EAN: 2147483647
Year: 2006
Pages: 291

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net