1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! The Memento design pattern example module
//! 
//! In this exercise, the Memento pattern is used to take snapshots of a text
//! object so as to form an undo list of changes to the text object.  Undoing
//! an operation means restoring a snapshot of the text object.
//! 
//! The undo list is implemented as a stack of memento objects that each
//! represent a snapshot of the text object taken before each operation is
//! applied.  After all operations are applied, the mementos are used to
//! restore the text object in reverse order, effectively undoing each
//! operation in turn.
//!
//! Accessed through the memento_exercise() function.

//-----------------------------------------------------------------------------

pub mod memento_textobject;

//-----------------------------------------------------------------------------

use memento_textobject::{Memento, MementoTextObject};

//-----------------------------------------------------------------------------

/// This struct creates a context around the undo list that the
/// memento_exercise() executes within.  This gets around the problem of
/// needing a static undo list as all the methods on this context have ready
/// access to the undo list in the context.
struct MementoContext {
    /// The list of memento objects that form a series of snapshots in time
    /// of a MementoTextObject.
    undo_list: Vec<Memento>,
}

impl MementoContext {
    /// Constructor.
    fn new() -> MementoContext {
        MementoContext {
            undo_list: vec![]
        }
    }


    /// Take a snapshot of the given text object associated with the name of
    /// given operation.
    ///
    /// # Parameters
    /// - text_object
    ///
    ///   The MementoTextObject to take a snapshot of.
    /// - operation
    ///
    ///   A string describing the operation that will be applied after the
    ///   snapshot is taken.
    fn save_for_undo(&mut self, text_object: &MementoTextObject, operation: &str) {
        self.undo_list.push(text_object.get_memento(operation));
    }

    /// An operation to search and replace text in a MementoTextObject.
    ///
    /// # Parameters
    /// - text_object
    ///
    ///   The MementoTextObject to work with.
    /// - search_pattern
    ///
    ///   What to look for in the MementoTextObject
    /// - replace_text
    ///
    ///   What to replace the `search_pattern` with
    fn operation_replace(&mut self, text_object: &mut MementoTextObject, search_pattern: &str, replace_text: &str) {
        let text = text_object.text().to_string();
        text_object.set_text(&text.replace(search_pattern, replace_text));
    }

    /// An operation to reverse the characters in the given MementoTextObject.
    ///
    /// # Parameters
    /// - text_object
    ///
    ///   The MementoTextObject to work with.
    fn operation_reverse(&mut self, text_object: &mut MementoTextObject) {
        let text = text_object.text().to_string();
        text_object.set_text(&text.chars().rev().collect::<String>());
    }

    /// Perform an undo on the given Command_TextObject, using the mementos in the
    /// "global" undo list.  If the undo list is empty, nothing happens.
    ///
    /// # Parameters
    /// - text_object
    ///
    ///   The MementoTextObject to update.
    fn undo(&mut self, text_object: &mut MementoTextObject) {
        if !self.undo_list.is_empty() {
            let last_memento = self.undo_list.pop().unwrap();
            text_object.restore_memento(&last_memento);

            // Show off what we (un)did.
            println!("    undoing operation {0:<31}: \"{1}\"", last_memento.name(), text_object);
        }
    }

    /// Helper function to replace a pattern with another string in the
    /// given MementoTextObject after adding a snapshot of the text
    /// object to the undo list.  Finally, it shows off what was done.
    ///
    /// # Parameters
    /// - text_object
    ///
    ///   The MementoTextObject to update.
    /// - search_pattern
    ///
    ///   What to look for in the MementoTextObject
    /// - replace_text
    ///
    ///   What to replace the `search_pattern` with
    fn apply_replace_operation(&mut self, text_object: &mut MementoTextObject, search_pattern: &str, replace_text: &str) {
        let operation_name = format!("Replace '{0}' with '{1}'", search_pattern, replace_text);
        self.save_for_undo(text_object, &operation_name);
        self.operation_replace(text_object, search_pattern, replace_text);
        println!("    operation {0:<31}: \"{1}\"", operation_name, text_object);
    }

    /// Helper function to reverse the order of the characters in the
    /// given MementoTextObject after adding a snapshot of the text
    /// object to an undo list.  Finally, it shows what was done.
    ///
    /// # Parameters
    /// - text_object
    ///
    ///   The MementoTextObject to work with.
    fn apply_reverse_operation(&mut self, text_object: &mut MementoTextObject) {
        let operation_name = "Reverse";
        self.save_for_undo(text_object, operation_name);
        self.operation_reverse(text_object);
        println!("    operation {0:<31}: \"{1}\"", operation_name, text_object);
    }

}

/// Example of using the "Memento" design pattern.
/// 
/// In this exercise, the Memento pattern is used to take snapshots of a text
/// object so as to form an undo list of changes to the text object.  Undoing
/// an operation means restoring a snapshot of the text object.
/// 
/// The undo list is implemented as a stack of memento objects that each
/// represent a snapshot of the text object taken before each operation is
/// applied.  After all operations are applied, the mementos are used to
/// restore the text object in reverse order, effectively undoing each
/// operation in turn.
/// 
/// Compare this to the command_exercise() and note that the steps taken there
/// are identical to here (except for method names, of course).  The difference
/// lies in how operations are executed and undone.  Mementos make the undo
/// process much cleaner and faster since operations do not need to be applied
/// repeatedly to get the text object into a specific state.  Specifically,
/// compare command_undo() with memento_undo().  Also note the differences in
/// the "memento_applyXXOperation()" methods, which more cleanly separate the
/// save from the operation.
// ! [Using Memento in Rust]
pub fn memento_exercise() -> Result<(), String> {
    println!("");
    println!("Memento Exercise");

    // Start with a fresh undo list.
    let mut memento_context = MementoContext::new();

    // The base text object to work from.
    let mut text_object = MementoTextObject::new("This is a line of text on which to experiment.");

    println!("  Starting text: \"{0}\"", text_object);

    // Apply four operations to the text.
    memento_context.apply_replace_operation(&mut text_object, "text", "painting");
    memento_context.apply_replace_operation(&mut text_object, "on", "off");
    memento_context.apply_reverse_operation(&mut text_object);
    memento_context.apply_replace_operation(&mut text_object, "i", "!");

    println!("  Now perform undo until back to original");

    // Now undo the four operations.
    memento_context.undo(&mut text_object);
    memento_context.undo(&mut text_object);
    memento_context.undo(&mut text_object);
    memento_context.undo(&mut text_object);

    println!("  Final text   : \"{0}\"", text_object);

    println!("  Done.");

    Ok(())
}
// ! [Using Memento in Rust]