|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| ScriptEditor.java | 16.4% | 30.5% | 36.7% | 27.6% |
|
||||||||||||||
| 1 |
package abbot.editor;
|
|
| 2 |
|
|
| 3 |
import java.awt.*;
|
|
| 4 |
import java.awt.event.*;
|
|
| 5 |
import java.io.*;
|
|
| 6 |
import java.lang.reflect.*;
|
|
| 7 |
import java.net.URL;
|
|
| 8 |
import java.util.*;
|
|
| 9 |
import java.util.List;
|
|
| 10 |
|
|
| 11 |
import javax.swing.*;
|
|
| 12 |
import javax.swing.filechooser.FileFilter;
|
|
| 13 |
import javax.swing.event.*;
|
|
| 14 |
|
|
| 15 |
import junit.extensions.abbot.*;
|
|
| 16 |
import junit.runner.TestCollector;
|
|
| 17 |
import abbot.BugReport;
|
|
| 18 |
import abbot.Log;
|
|
| 19 |
import abbot.ExitException;
|
|
| 20 |
import abbot.NoExitSecurityManager;
|
|
| 21 |
import abbot.AssertionFailedError;
|
|
| 22 |
import abbot.Platform;
|
|
| 23 |
import abbot.editor.actions.*;
|
|
| 24 |
import abbot.editor.editors.*;
|
|
| 25 |
import abbot.editor.recorder.*;
|
|
| 26 |
import abbot.editor.widgets.*;
|
|
| 27 |
import abbot.editor.widgets.TextField;
|
|
| 28 |
import abbot.finder.*;
|
|
| 29 |
import abbot.i18n.Strings;
|
|
| 30 |
import abbot.script.*;
|
|
| 31 |
import abbot.script.Action;
|
|
| 32 |
import abbot.script.Resolver;
|
|
| 33 |
import abbot.tester.*;
|
|
| 34 |
import abbot.tester.Robot;
|
|
| 35 |
import abbot.util.*;
|
|
| 36 |
import abbot.util.Properties;
|
|
| 37 |
import abbot.util.ThreadTerminatingSecurityManager.ThreadTerminatedException;
|
|
| 38 |
|
|
| 39 |
import com.apple.mrj.*;
|
|
| 40 |
|
|
| 41 |
/**
|
|
| 42 |
* This is the 'model' behind the script editor UI.<p>
|
|
| 43 |
*
|
|
| 44 |
* Acts as a resolver, using the currently in-context script as the component
|
|
| 45 |
* resolver. <p>
|
|
| 46 |
*/
|
|
| 47 |
|
|
| 48 |
/* To add new actions, add the action to the list in initActions(),
|
|
| 49 |
* and optionally add it to the menu layout in initMenus. Define a name for
|
|
| 50 |
* it in EditorConstants, and an inner Action class for it which uses that
|
|
| 51 |
* name.
|
|
| 52 |
*/
|
|
| 53 |
// Apologies for the extreme cruftiness and lack of proper factoring. This
|
|
| 54 |
// was written at the same time as the underlying framework, and refactored
|
|
| 55 |
// (sort of) into model/view at the same time, so it's hardly a shining
|
|
| 56 |
// example of clean design. Don't know if it would have been any better
|
|
| 57 |
// written TDD, though.
|
|
| 58 |
public class ScriptEditor |
|
| 59 |
implements ActionListener, Resolver, EditorConstants {
|
|
| 60 |
|
|
| 61 |
private static int selectKey; |
|
| 62 |
private static int captureKey; |
|
| 63 |
private static int captureImageKey; |
|
| 64 |
|
|
| 65 |
static {
|
|
| 66 | 2 |
try {
|
| 67 | 2 |
new EventExceptionHandler().install();
|
| 68 |
} |
|
| 69 |
catch(Exception e) {
|
|
| 70 |
// Ignore for now
|
|
| 71 |
} |
|
| 72 | 2 |
String key = System.getProperty("abbot.editor.select_key", "F1"); |
| 73 | 2 |
selectKey = KeyStroke.getKeyStroke(key).getKeyCode(); |
| 74 | 2 |
key = System.getProperty("abbot.editor.capture_key", "F2"); |
| 75 | 2 |
captureKey = KeyStroke.getKeyStroke(key).getKeyCode(); |
| 76 | 2 |
key = System.getProperty("abbot.editor.capture_image_key", "F3"); |
| 77 | 2 |
captureImageKey = KeyStroke.getKeyStroke(key).getKeyCode(); |
| 78 |
} |
|
| 79 |
|
|
| 80 |
// if set, log all events, even those going to filtered components
|
|
| 81 |
private static final boolean LOG_ALL_EVENTS = |
|
| 82 |
Boolean.getBoolean("abbot.editor.log_all_events");
|
|
| 83 |
private static final long FIXTURE_EVENT_MASK = |
|
| 84 |
Long.getLong("abbot.fixture.event_mask",
|
|
| 85 |
EventRecorder.RECORDING_EVENT_MASK).longValue(); |
|
| 86 |
|
|
| 87 |
/** Key to use to invert an assertion/wait. */
|
|
| 88 |
public static final int KC_INVERT = KeyEvent.VK_SHIFT; |
|
| 89 |
/** Key to use to insert a wait instead of an assertion. Use option key
|
|
| 90 |
on mac, control key anywhere else. */
|
|
| 91 |
public static final int KC_WAIT = |
|
| 92 | 2 |
Platform.isMacintosh() ? KeyEvent.VK_ALT : KeyEvent.VK_CONTROL; |
| 93 |
/** Flag for informational status. */
|
|
| 94 |
private static final int INFO = 0; |
|
| 95 |
/** Flag to indicate a warning. */
|
|
| 96 |
private static final int WARN = 1; |
|
| 97 |
/** Flag to indicate an error. */
|
|
| 98 |
private static final int ERROR = 2; |
|
| 99 |
/** Flag to indicate a script failure. */
|
|
| 100 |
private static final int FAILURE = 3; |
|
| 101 |
/** Prefixes for different types of status messages. */
|
|
| 102 |
private static final String[] statusFormat = |
|
| 103 |
{ "Normal", "Warning", "Error", "Failure" };
|
|
| 104 |
private static final Color[] statusColor = { |
|
| 105 |
Color.black, |
|
| 106 |
Color.orange.darker(), |
|
| 107 |
Color.red, |
|
| 108 |
Color.red, |
|
| 109 |
}; |
|
| 110 |
|
|
| 111 |
private ArrayList insertActions = new ArrayList(); |
|
| 112 |
private ArrayList assertActions = new ArrayList(); |
|
| 113 |
private ArrayList waitActions = new ArrayList(); |
|
| 114 |
private ArrayList captureActions = new ArrayList(); |
|
| 115 |
|
|
| 116 |
/** Adapter for representing the script itself, providing access to
|
|
| 117 |
* individual script steps.
|
|
| 118 |
*/
|
|
| 119 |
private ScriptModel scriptModel;
|
|
| 120 |
private ScriptTable scriptTable;
|
|
| 121 |
private EditorSecurityManager securityManager;
|
|
| 122 |
private SecurityManager oldSecurityManager;
|
|
| 123 |
|
|
| 124 |
private int nonce; |
|
| 125 |
/** Keep all application under test threads in the same group to make them
|
|
| 126 |
* easier to track. */
|
|
| 127 |
private ThreadGroup appGroup;
|
|
| 128 |
private TestHierarchy hierarchy;
|
|
| 129 |
private Hierarchy oldHierarchy;
|
|
| 130 |
private Recorder[] recorders;
|
|
| 131 |
private java.util.List savedStateWhileRecording;
|
|
| 132 |
/** Allow exits from anywhere until the editor is fully initialized. */
|
|
| 133 |
private boolean rootIsExiting = true; |
|
| 134 |
private boolean exiting; |
|
| 135 |
private boolean hiding; |
|
| 136 |
private boolean ignoreStepEvents; |
|
| 137 |
/** Whether to ignore incoming AWT events. */
|
|
| 138 |
private boolean ignoreEvents; |
|
| 139 |
/** Is there a script or launch step currently running? */
|
|
| 140 |
private boolean isScriptRunning; |
|
| 141 |
/** When was some portion of the app exercised? */
|
|
| 142 |
private long lastLaunchTime; |
|
| 143 |
/** Are we trying to capture an image? */
|
|
| 144 |
private boolean capturingImage; |
|
| 145 |
/** What component is currently "selected" for capture? */
|
|
| 146 |
private Component captureComponent;
|
|
| 147 |
private Component innermostCaptureComponent;
|
|
| 148 |
private Highlighter highlighter;
|
|
| 149 |
/** Is this the first editor launched, or one under test? */
|
|
| 150 |
private boolean isRootEditor = true; |
|
| 151 |
/** AWT input state. */
|
|
| 152 |
private static InputState state = Robot.getState(); |
|
| 153 |
/** Generic filter to select a test script. */
|
|
| 154 |
private ScriptFilter filter = new ScriptFilter(); |
|
| 155 |
|
|
| 156 |
/** Current test case class (should derive from AWTTestCase). */
|
|
| 157 |
private Class testClass;
|
|
| 158 |
/** Current test suite. */
|
|
| 159 |
private ScriptTestSuite testSuite;
|
|
| 160 |
/** Current test script. */
|
|
| 161 |
private Script testScript;
|
|
| 162 |
/** Is the current script a temporary placeholder? */
|
|
| 163 |
private File tempFile;
|
|
| 164 |
/** Runner used to execute the script. */
|
|
| 165 |
private StepRunner runner;
|
|
| 166 |
/** Current set of scripts, based on the test suite (if any). */
|
|
| 167 |
private List testScriptList;
|
|
| 168 |
/** Currently selected component. Note that this may be a dummy
|
|
| 169 |
* component.
|
|
| 170 |
*/
|
|
| 171 |
private Component selectedComponent;
|
|
| 172 |
/** Currently selected reference, if any. */
|
|
| 173 |
private ComponentReference selectedReference;
|
|
| 174 |
|
|
| 175 |
/** Are we currently recording events? */
|
|
| 176 |
private boolean recording; |
|
| 177 |
/** The current recorder to pass events for capture. */
|
|
| 178 |
private Recorder recorder;
|
|
| 179 |
/** Since recorder starts with a key release, and stops with a key press,
|
|
| 180 |
make sure we don't start immediately after stopping.
|
|
| 181 |
*/
|
|
| 182 |
private boolean justStoppedRecording; |
|
| 183 |
/** Need to be able to set the combo box selection w/o reacting to the
|
|
| 184 |
* resulting posted action.
|
|
| 185 |
*/
|
|
| 186 |
private boolean ignoreComboBox; |
|
| 187 |
/** Where to stop. */
|
|
| 188 |
private Step stopStep;
|
|
| 189 |
|
|
| 190 |
// GUI components
|
|
| 191 |
private JFileChooser chooser;
|
|
| 192 |
private ScriptEditorFrame view;
|
|
| 193 |
private ComboBoxModel model;
|
|
| 194 |
|
|
| 195 |
private boolean invertAssertions; |
|
| 196 |
private boolean waitAssertions; |
|
| 197 |
private ActionMap actionMap;
|
|
| 198 |
private String name;
|
|
| 199 |
|
|
| 200 |
/**
|
|
| 201 |
* Constructs a ScriptEditor which handles script editing logic.
|
|
| 202 |
* ScriptEditorFrame provides the view/controller.
|
|
| 203 |
* @see ScriptEditorFrame
|
|
| 204 |
*/
|
|
| 205 | 2 |
public ScriptEditor() {
|
| 206 |
|
|
| 207 | 2 |
if (Boolean.getBoolean("abbot.framework.launched")) { |
| 208 | 0 |
isRootEditor = false;
|
| 209 | 0 |
name = "Script Editor (under test)";
|
| 210 |
} |
|
| 211 |
else {
|
|
| 212 | 2 |
System.setProperty("abbot.framework.launched", "true"); |
| 213 | 2 |
name = "Script Editor (root)";
|
| 214 |
} |
|
| 215 |
|
|
| 216 |
// TODO: clean this up
|
|
| 217 | 2 |
actionMap = initActions(); |
| 218 | 2 |
hierarchy = initContext(isRootEditor); |
| 219 | 2 |
recorders = initRecorders(); |
| 220 | 2 |
view = initFrame(isRootEditor); |
| 221 | 2 |
hierarchy.setFiltered(view, true);
|
| 222 | 2 |
updateDynamicActions(view); |
| 223 |
|
|
| 224 | 2 |
view.setComponentBrowser(createComponentBrowser()); |
| 225 | 2 |
addEventHandlers(view); |
| 226 |
|
|
| 227 |
// Clear the status only if there were no errors
|
|
| 228 | 2 |
if (view.getStatus().equals(Strings.get("Initializing"))) { |
| 229 | 2 |
setStatus(Strings.get("Ready"));
|
| 230 |
} |
|
| 231 | 2 |
rootIsExiting = false;
|
| 232 |
} |
|
| 233 |
|
|
| 234 |
/** Provides a convenient menu setup definition.
|
|
| 235 |
Use a defined action name to indicate that action's place within the
|
|
| 236 |
menu. Null values indicate menu separators.
|
|
| 237 |
*/
|
|
| 238 | 2 |
private String[][] initMenus() {
|
| 239 | 2 |
ArrayList fileMenu = new ArrayList();
|
| 240 | 2 |
ArrayList helpMenu = new ArrayList();
|
| 241 | 2 |
fileMenu.addAll(Arrays.asList(new String[] {
|
| 242 |
MENU_FILE, |
|
| 243 |
ACTION_SCRIPT_NEW, |
|
| 244 |
ACTION_SCRIPT_DUPLICATE, |
|
| 245 |
ACTION_SCRIPT_OPEN, |
|
| 246 |
null,
|
|
| 247 |
ACTION_SCRIPT_SAVE, |
|
| 248 |
ACTION_SCRIPT_SAVE_AS, |
|
| 249 |
ACTION_SCRIPT_RENAME, |
|
| 250 |
ACTION_SCRIPT_CLOSE, |
|
| 251 |
null,
|
|
| 252 |
ACTION_SCRIPT_DELETE, |
|
| 253 |
})); |
|
| 254 |
|
|
| 255 | 2 |
helpMenu.add(MENU_HELP); |
| 256 | 2 |
if (!Platform.isOSX()) {
|
| 257 | 2 |
fileMenu.add(null);
|
| 258 | 2 |
fileMenu.add(ACTION_EDITOR_QUIT); |
| 259 | 2 |
helpMenu.add(ACTION_EDITOR_ABOUT); |
| 260 |
} |
|
| 261 | 2 |
helpMenu.addAll(Arrays.asList(new String[] {
|
| 262 |
ACTION_EDITOR_USERGUIDE, |
|
| 263 |
ACTION_EDITOR_WEBSITE, |
|
| 264 |
ACTION_EDITOR_EMAIL, |
|
| 265 |
ACTION_EDITOR_BUGREPORT, |
|
| 266 |
})); |
|
| 267 |
|
|
| 268 | 2 |
return new String[][] { |
| 269 |
(String[])fileMenu.toArray(new String[fileMenu.size()]),
|
|
| 270 |
{
|
|
| 271 |
MENU_EDIT, |
|
| 272 |
ACTION_STEP_CUT, |
|
| 273 |
null,
|
|
| 274 |
ACTION_STEP_MOVE_UP, |
|
| 275 |
ACTION_STEP_MOVE_DOWN, |
|
| 276 |
ACTION_STEP_GROUP, |
|
| 277 |
null,
|
|
| 278 |
ACTION_SELECT_COMPONENT, |
|
| 279 |
null,
|
|
| 280 |
ACTION_SCRIPT_CLEAR, |
|
| 281 |
}, |
|
| 282 |
{
|
|
| 283 |
MENU_TEST, |
|
| 284 |
ACTION_RUN, |
|
| 285 |
ACTION_RUN_TO, |
|
| 286 |
ACTION_RUN_SELECTED, |
|
| 287 |
null,
|
|
| 288 |
ACTION_EXPORT_HIERARCHY, |
|
| 289 |
null,
|
|
| 290 |
ACTION_RUN_LAUNCH, |
|
| 291 |
ACTION_RUN_TERMINATE, |
|
| 292 |
null,
|
|
| 293 |
ACTION_TOGGLE_STOP_ON_FAILURE, |
|
| 294 |
ACTION_TOGGLE_STOP_ON_ERROR, |
|
| 295 |
ACTION_TOGGLE_FORKED, |
|
| 296 |
ACTION_GET_VMARGS, |
|
| 297 |
ACTION_TOGGLE_SLOW_PLAYBACK, |
|
| 298 |
ACTION_TOGGLE_AWT_MODE, |
|
| 299 |
}, |
|
| 300 |
{
|
|
| 301 |
MENU_INSERT, |
|
| 302 |
ACTION_INSERT_ANNOTATION, |
|
| 303 |
ACTION_INSERT_APPLET, |
|
| 304 |
ACTION_INSERT_CALL, |
|
| 305 |
ACTION_INSERT_COMMENT, |
|
| 306 |
ACTION_INSERT_EXPRESSION, |
|
| 307 |
ACTION_INSERT_LAUNCH, |
|
| 308 |
ACTION_INSERT_FIXTURE, |
|
| 309 |
ACTION_INSERT_SAMPLE, |
|
| 310 |
ACTION_INSERT_SCRIPT, |
|
| 311 |
ACTION_INSERT_SEQUENCE, |
|
| 312 |
ACTION_INSERT_TERMINATE, |
|
| 313 |
}, |
|
| 314 |
{
|
|
| 315 |
MENU_CAPTURE, |
|
| 316 |
}, |
|
| 317 |
(String[])helpMenu.toArray(new String[helpMenu.size()]),
|
|
| 318 |
}; |
|
| 319 |
} |
|
| 320 |
|
|
| 321 |
// All editor actions should be defined here
|
|
| 322 | 2 |
private ActionMap initActions() {
|
| 323 | 2 |
javax.swing.Action[] actions = {
|
| 324 |
new EditorAboutAction(),
|
|
| 325 |
new EditorEmailAction(),
|
|
| 326 |
new EditorBugReportAction(),
|
|
| 327 |
new EditorWebsiteAction(),
|
|
| 328 |
new EditorUserGuideAction(),
|
|
| 329 |
new EditorQuitAction(),
|
|
| 330 |
new ScriptOpenAction(),
|
|
| 331 |
new ScriptNewAction(),
|
|
| 332 |
new ScriptDuplicateAction(),
|
|
| 333 |
new ScriptSaveAction(),
|
|
| 334 |
new ScriptSaveAsAction(),
|
|
| 335 |
new ScriptRenameAction(),
|
|
| 336 |
new ScriptCloseAction(),
|
|
| 337 |
new ScriptDeleteAction(),
|
|
| 338 |
new ScriptClearAction(),
|
|
| 339 |
new StepCutAction(),
|
|
| 340 |
new StepMoveUpAction(),
|
|
| 341 |
new StepMoveDownAction(),
|
|
| 342 |
new StepGroupAction(),
|
|
| 343 |
new RunAction(),
|
|
| 344 |
new RunToAction(),
|
|
| 345 |
new RunSelectedAction(),
|
|
| 346 |
new RunLaunchAction(),
|
|
| 347 |
new RunTerminateAction(),
|
|
| 348 |
new GetVMArgsAction(),
|
|
| 349 |
new SelectTestSuiteAction(),
|
|
| 350 |
new ExportHierarchyAction(),
|
|
| 351 |
new ToggleForkedAction(),
|
|
| 352 |
new InsertLaunchAction(),
|
|
| 353 |
new InsertFixtureAction(),
|
|
| 354 |
new InsertAppletAction(),
|
|
| 355 |
new InsertTerminateAction(),
|
|
| 356 |
new InsertCallAction(),
|
|
| 357 |
new InsertSampleAction(),
|
|
| 358 |
new InsertSequenceAction(),
|
|
| 359 |
new InsertScriptAction(),
|
|
| 360 |
new InsertCommentAction(),
|
|
| 361 |
new InsertExpressionAction(),
|
|
| 362 |
new InsertAnnotationAction(),
|
|
| 363 |
new ToggleStopOnFailureAction(),
|
|
| 364 |
new ToggleStopOnErrorAction(),
|
|
| 365 |
new ToggleSlowPlaybackAction(),
|
|
| 366 |
new ToggleAWTModeAction(),
|
|
| 367 |
new CaptureImageAction(),
|
|
| 368 |
new CaptureComponentAction(),
|
|
| 369 |
new SelectComponentAction(),
|
|
| 370 |
}; |
|
| 371 | 2 |
ActionMap map = new ActionMap();
|
| 372 | 2 |
for (int i=0;i < actions.length;i++) { |
| 373 | 92 |
Object key = actions[i].getValue(EditorAction.ACTION_KEY); |
| 374 | 92 |
map.put(key, actions[i]); |
| 375 |
} |
|
| 376 | 2 |
return map;
|
| 377 |
} |
|
| 378 |
|
|
| 379 |
/**
|
|
| 380 |
* Add event handlers to their respective components
|
|
| 381 |
*/
|
|
| 382 | 2 |
private void addEventHandlers(final ScriptEditorFrame view) { |
| 383 | 2 |
scriptTable.getSelectionModel(). |
| 384 |
addListSelectionListener(new ScriptTableSelectionHandler());
|
|
| 385 | 2 |
scriptTable.addMouseListener(new MouseAdapter() {
|
| 386 | 0 |
public void mouseClicked(MouseEvent me) { |
| 387 | 0 |
if ((me.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
|
| 388 | 0 |
if (view.getEditor() == null) |
| 389 | 0 |
setStepEditor(); |
| 390 |
} |
|
| 391 |
} |
|
| 392 |
}); |
|
| 393 | 2 |
MouseListener ml = new MouseAdapter() {
|
| 394 | 0 |
public void mouseClicked(MouseEvent me) { |
| 395 | 0 |
if ((me.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
|
| 396 | 0 |
int size = scriptTable.getRowCount();
|
| 397 | 0 |
scriptTable.clearSelection(); |
| 398 | 0 |
scriptTable.setCursorLocation(size); |
| 399 |
} |
|
| 400 |
} |
|
| 401 |
}; |
|
| 402 | 2 |
view.addMouseListener(ml); |
| 403 | 2 |
view.getTestScriptSelector().addItemListener(new ScriptSelectorItemHandler());
|
| 404 | 2 |
view.addWindowListener(new WindowAdapter() {
|
| 405 | 0 |
public void windowClosing(WindowEvent e) { |
| 406 | 0 |
quitApplication(); |
| 407 |
} |
|
| 408 |
}); |
|
| 409 | 2 |
if (Platform.isOSX()) {
|
| 410 |
// Mac has it's own dedicated Quit/About menu items
|
|
| 411 | 0 |
MRJApplicationUtils.registerQuitHandler(new MRJQuitHandler() {
|
| 412 | 0 |
public void handleQuit() { |
| 413 | 0 |
quitApplication(); |
| 414 |
} |
|
| 415 |
}); |
|
| 416 | 0 |
MRJApplicationUtils.registerAboutHandler(new MRJAboutHandler() {
|
| 417 | 0 |
public void handleAbout() { |
| 418 | 0 |
view.showAboutBox(); |
| 419 |
} |
|
| 420 |
}); |
|
| 421 |
} |
|
| 422 |
} |
|
| 423 |