Line 0
Link Here
|
|
|
1 |
package org.apache.jmeter.gui.tree; |
2 |
|
3 |
import java.awt.event.ActionEvent; |
4 |
import java.util.ArrayList; |
5 |
import java.util.Map.Entry; |
6 |
import javax.swing.event.TreeModelEvent; |
7 |
import javax.swing.event.TreeModelListener; |
8 |
import javax.swing.tree.TreePath; |
9 |
import org.apache.jmeter.engine.TreeCloner; |
10 |
import org.apache.jmeter.gui.GuiPackage; |
11 |
import org.apache.jmeter.gui.action.Load; |
12 |
import org.apache.jmeter.gui.action.UndoCommand; |
13 |
import org.apache.jorphan.collections.HashTree; |
14 |
import org.apache.jorphan.logging.LoggingManager; |
15 |
import org.apache.log.Logger; |
16 |
|
17 |
/** |
18 |
* Users expected record situations: initial empty tree; before node deletion; |
19 |
* before node insertion; after each walk off edited node (modifyTestElement) |
20 |
* |
21 |
* @author apc@apc.kg |
22 |
*/ |
23 |
public class UndoHistory |
24 |
implements TreeModelListener { |
25 |
|
26 |
private static final int INITIAL_POS = -1; |
27 |
private static final Logger log = LoggingManager.getLoggerForClass(); |
28 |
|
29 |
private static class HistoryItem { |
30 |
|
31 |
private final HashTree tree; |
32 |
private final TreePath path; |
33 |
// maybe the comment should be removed since it is not used yet |
34 |
private final String comment; |
35 |
|
36 |
public HistoryItem(HashTree copy, TreePath apath, String acomment) { |
37 |
tree = copy; |
38 |
path = apath; |
39 |
comment = acomment; |
40 |
} |
41 |
|
42 |
public HashTree getKey() { |
43 |
return tree; |
44 |
} |
45 |
|
46 |
public TreePath getValue() { |
47 |
return path; |
48 |
} |
49 |
|
50 |
public String getComment() { |
51 |
return comment; |
52 |
} |
53 |
} |
54 |
private ArrayList<HistoryItem> history = new ArrayList<HistoryItem>(); |
55 |
private int position = INITIAL_POS; |
56 |
/** |
57 |
* flag to prevent recursive actions |
58 |
*/ |
59 |
private boolean working = false; |
60 |
|
61 |
public UndoHistory() { |
62 |
} |
63 |
|
64 |
public void clear() { |
65 |
if (working) { |
66 |
return; |
67 |
} |
68 |
log.debug("Clearing history", new Throwable()); |
69 |
history.clear(); |
70 |
position = INITIAL_POS; |
71 |
} |
72 |
|
73 |
/** |
74 |
* this method relies on the rule that the record in history made AFTER |
75 |
* change has been made to test plan |
76 |
* |
77 |
* @param treeModel |
78 |
* @param path |
79 |
* @param comment |
80 |
*/ |
81 |
void add(JMeterTreeModel treeModel, TreePath path, String comment) { |
82 |
// don't add element if we are in the middle of undo/redo |
83 |
if (working) { |
84 |
return; |
85 |
} |
86 |
|
87 |
log.debug("Adding history element", new Throwable()); |
88 |
JMeterTreeNode root = (JMeterTreeNode) ((JMeterTreeNode) treeModel.getRoot()); |
89 |
if (root.getChildCount() < 1) { |
90 |
return; |
91 |
} |
92 |
|
93 |
working = true; |
94 |
// get test plan tree |
95 |
HashTree tree = treeModel.getCurrentSubTree((JMeterTreeNode) treeModel.getRoot()); |
96 |
// first clone to not convert original tree |
97 |
tree = (HashTree) tree.getTree(tree.getArray()[0]).clone(); |
98 |
|
99 |
position++; |
100 |
while (history.size() > position) { |
101 |
log.debug("Removing further record, position: " + position + ", size: " + history.size()); |
102 |
history.remove(history.size() - 1); |
103 |
} |
104 |
|
105 |
// convert before clone! |
106 |
UndoCommand.convertSubTree(tree); |
107 |
TreeCloner cloner = new TreeCloner(false); |
108 |
tree.traverse(cloner); |
109 |
HashTree copy = cloner.getClonedTree(); |
110 |
|
111 |
|
112 |
history.add(new HistoryItem(copy, path, comment)); |
113 |
|
114 |
log.debug("Added history element, position: " + position + ", size: " + history.size()); |
115 |
working = false; |
116 |
} |
117 |
|
118 |
public TreePath getRelativeState(int offset, JMeterTreeModel acceptorModel) { |
119 |
log.debug("Moving history from position " + position + " with step " + offset + ", size is " + history.size()); |
120 |
if (offset < 0 && !canUndo()) { |
121 |
log.warn("Can't undo, we're already on the last record"); |
122 |
return null; |
123 |
} |
124 |
|
125 |
if (offset > 0 && !canRedo()) { |
126 |
log.warn("Can't redo, we're already on the first record"); |
127 |
return null; |
128 |
} |
129 |
|
130 |
position += offset; |
131 |
|
132 |
if (!history.isEmpty()) { |
133 |
HashTree newModel = history.get(position).getKey(); |
134 |
acceptorModel.removeTreeModelListener(this); |
135 |
working = true; |
136 |
try { |
137 |
boolean res = Load.insertLoadedTree(ActionEvent.ACTION_PERFORMED, newModel); |
138 |
if (!res) { |
139 |
throw new RuntimeException("Loaded data is not TestPlan"); |
140 |
} |
141 |
|
142 |
} catch (Exception ex) { |
143 |
log.error("Failed to load from history", ex); |
144 |
} |
145 |
acceptorModel.addTreeModelListener(this); |
146 |
working = false; |
147 |
} |
148 |
log.debug("Current position " + position + ", size is " + history.size()); |
149 |
// select historical path |
150 |
return history.get(position).getValue(); |
151 |
} |
152 |
|
153 |
public boolean canRedo() { |
154 |
return position < history.size() - 1; |
155 |
} |
156 |
|
157 |
public boolean canUndo() { |
158 |
return position > INITIAL_POS + 1; |
159 |
} |
160 |
|
161 |
public void treeNodesChanged(TreeModelEvent tme) { |
162 |
log.debug("Nodes changed"); |
163 |
} |
164 |
|
165 |
// is there better way to record test plan load events? |
166 |
// currently it records each node added separately |
167 |
public void treeNodesInserted(TreeModelEvent tme) { |
168 |
log.debug("Nodes inserted"); |
169 |
final JMeterTreeModel sender = (JMeterTreeModel) tme.getSource(); |
170 |
add(sender, getTreePathToRecord(tme), "Add"); |
171 |
} |
172 |
|
173 |
public void treeNodesRemoved(TreeModelEvent tme) { |
174 |
log.debug("Nodes removed"); |
175 |
add((JMeterTreeModel) tme.getSource(), getTreePathToRecord(tme), "Remove"); |
176 |
} |
177 |
|
178 |
public void treeStructureChanged(TreeModelEvent tme) { |
179 |
log.debug("Nodes struct changed"); |
180 |
add((JMeterTreeModel) tme.getSource(), getTreePathToRecord(tme), "Complex Change"); |
181 |
} |
182 |
|
183 |
private TreePath getTreePathToRecord(TreeModelEvent tme) { |
184 |
TreePath path; |
185 |
if (GuiPackage.getInstance() != null) { |
186 |
path = GuiPackage.getInstance().getMainFrame().getTree().getSelectionPath(); |
187 |
} else { |
188 |
path = tme.getTreePath(); |
189 |
} |
190 |
return path; |
191 |
} |
192 |
} |