0001: package abbot.editor;
0002:
0003: import java.awt.*;
0004: import java.awt.event.*;
0005: import java.io.*;
0006: import java.lang.reflect.*;
0007: import java.net.URL;
0008: import java.util.*;
0009: import java.util.List;
0010:
0011: import javax.swing.*;
0012: import javax.swing.filechooser.FileFilter;
0013: import javax.swing.event.*;
0014:
0015: import junit.extensions.abbot.*;
0016: import abbot.BugReport;
0017: import abbot.Log;
0018: import abbot.ExitException;
0019: import abbot.NoExitSecurityManager;
0020: import abbot.AssertionFailedError;
0021: import abbot.Platform;
0022: import abbot.editor.actions.*;
0023: import abbot.editor.editors.*;
0024: import abbot.editor.recorder.*;
0025: import abbot.editor.widgets.*;
0026: import abbot.editor.widgets.TextField;
0027: import abbot.finder.*;
0028: import abbot.i18n.Strings;
0029: import abbot.script.*;
0030: import abbot.script.Action;
0031: import abbot.script.Resolver;
0032: import abbot.tester.*;
0033: import abbot.tester.Robot;
0034: import abbot.util.*;
0035:
0036: /**
0037: * This is the 'model' behind the script editor UI.<p>
0038: *
0039: * Acts as a resolver, using the currently in-context script as the component
0040: * resolver. <p>
0041: */
0042:
0043: /* To add new actions, add the action to the list in initActions(),
0044: * and optionally add it to the menu layout in initMenus. Define a name for
0045: * it in EditorConstants, and an inner Action class for it which uses that
0046: * name.
0047: */
0048: // Apologies for the extreme cruftiness and lack of proper factoring. This
0049: // was written at the same time as the underlying framework, and refactored
0050: // (sort of) into model/view at the same time, so it's hardly a shining
0051: // example of clean design. Don't know if it would have been any better
0052: // written TDD, though.
0053: public class ScriptEditor implements ActionListener, Resolver,
0054: EditorConstants {
0055:
0056: private static int selectKey;
0057: private static int captureKey;
0058: private static int captureImageKey;
0059:
0060: static {
0061: try {
0062: new EventExceptionHandler().install();
0063: } catch (Exception e) {
0064: // Ignore for now
0065: }
0066: String key = System
0067: .getProperty("abbot.editor.select_key", "F1");
0068: selectKey = KeyStroke.getKeyStroke(key).getKeyCode();
0069: key = System.getProperty("abbot.editor.capture_key", "F2");
0070: captureKey = KeyStroke.getKeyStroke(key).getKeyCode();
0071: key = System
0072: .getProperty("abbot.editor.capture_image_key", "F3");
0073: captureImageKey = KeyStroke.getKeyStroke(key).getKeyCode();
0074: }
0075:
0076: // if set, log all events, even those going to filtered components
0077: private static final boolean LOG_ALL_EVENTS = Boolean
0078: .getBoolean("abbot.editor.log_all_events");
0079:
0080: private static final long FIXTURE_EVENT_MASK = Long.getLong(
0081: "abbot.fixture.event_mask",
0082: EventRecorder.RECORDING_EVENT_MASK).longValue();
0083:
0084: /** Key to use to invert an assertion/wait. */
0085: public static final int KC_INVERT = KeyEvent.VK_SHIFT;
0086: /** Key to use to insert a wait instead of an assertion. Use option key
0087: on mac, control key anywhere else. */
0088: public static final int KC_WAIT = Platform.isMacintosh() ? KeyEvent.VK_ALT
0089: : KeyEvent.VK_CONTROL;
0090: /** Flag for informational status. */
0091: private static final int INFO = 0;
0092: /** Flag to indicate a warning. */
0093: private static final int WARN = 1;
0094: /** Flag to indicate an error. */
0095: private static final int ERROR = 2;
0096: /** Flag to indicate a script failure. */
0097: private static final int FAILURE = 3;
0098: /** Prefixes for different types of status messages. */
0099: private static final String[] statusFormat = { "Normal", "Warning",
0100: "Error", "Failure" };
0101: private static final Color[] statusColor = { Color.black,
0102: Color.orange.darker(), Color.red, Color.red, };
0103:
0104: private ArrayList insertActions = new ArrayList();
0105: private ArrayList assertActions = new ArrayList();
0106: private ArrayList waitActions = new ArrayList();
0107: private ArrayList captureActions = new ArrayList();
0108:
0109: /** Adapter for representing the script itself, providing access to
0110: * individual script steps.
0111: */
0112: private ScriptModel scriptModel;
0113: private ScriptTable scriptTable;
0114: private SecurityManager securityManager;
0115: private SecurityManager oldSecurityManager;
0116:
0117: private int nonce;
0118: /** Keep all application under test threads in the same group to make them
0119: * easier to track. */
0120: private ThreadGroup appGroup;
0121: private TestHierarchy hierarchy;
0122: private Hierarchy oldHierarchy;
0123: private Recorder[] recorders;
0124: private SpinningDialWaitIndicator waiter;
0125: /** Allow exits from anywhere until the editor is fully initialized. */
0126: private boolean rootIsExiting = true;
0127: private boolean exiting;
0128: private boolean hiding;
0129: private boolean ignoreStepEvents;
0130: /** Whether to ignore incoming AWT events. */
0131: private boolean ignoreEvents;
0132: /** Is there a script or launch step currently running? */
0133: private boolean isScriptRunning;
0134: /** When was some portion of the app exercised? */
0135: private long lastLaunchTime;
0136: /** Are we trying to capture an image? */
0137: private boolean capturingImage;
0138: /** What component is currently "selected" for capture? */
0139: private Component captureComponent;
0140: private Component innermostCaptureComponent;
0141: private Highlighter highlighter;
0142: /** Is this the first editor launched, or one under test? */
0143: private boolean isRootEditor = true;
0144: /** AWT input state. */
0145: private static InputState state = Robot.getState();
0146: /** Generic filter to select a test script. */
0147: private ScriptFilter filter = new ScriptFilter();
0148:
0149: /** Current test case class (should derive from AWTTestCase). */
0150: private Class testClass;
0151: /** Current test suite. */
0152: private ScriptTestSuite testSuite;
0153: /** Current test script. */
0154: private Script testScript;
0155: /** Is the current script a temporary placeholder? */
0156: private File tempFile;
0157: /** Runner used to execute the script. */
0158: private StepRunner runner;
0159: /** Current set of scripts, based on the test suite (if any). */
0160: private List testScriptList;
0161: /** Currently selected component. Note that this may be a dummy
0162: * component.
0163: */
0164: private Component selectedComponent;
0165: /** Currently selected reference, if any. */
0166: private ComponentReference selectedReference;
0167:
0168: /** Are we currently recording events? */
0169: private boolean recording;
0170: /** The current recorder to pass events for capture. */
0171: private Recorder recorder;
0172: /** Since recorder starts with a key release, and stops with a key press,
0173: make sure we don't start immediately after stopping.
0174: */
0175: private boolean justStoppedRecording;
0176: /** Need to be able to set the combo box selection w/o reacting to the
0177: * resulting posted action.
0178: */
0179: private boolean ignoreComboBox;
0180: /** Where to stop. */
0181: private Step stopStep;
0182:
0183: // GUI components
0184: private JFileChooser chooser;
0185: private ScriptEditorFrame view;
0186: private ComboBoxModel model;
0187:
0188: private boolean invertAssertions;
0189: private boolean waitAssertions;
0190: private ActionMap actionMap;
0191: private String name;
0192:
0193: /** The edtiro configuration */
0194: private EditorContext editorConfiguration;
0195:
0196: /**
0197: * Constructs a ScriptEditor which handles script editing logic.
0198: * ScriptEditorFrame provides the view/controller.
0199: * @see ScriptEditorFrame
0200: */
0201: public ScriptEditor(EditorContext ec) {
0202:
0203: editorConfiguration = ec;
0204:
0205: //
0206:
0207: if (ec.isEmbedded()) {
0208: name = "Script Editor (emdedded)";
0209: } else if (Boolean.getBoolean("abbot.framework.launched")) {
0210: isRootEditor = false;
0211: name = "Script Editor (under test)";
0212: } else {
0213: System.setProperty("abbot.framework.launched", "true");
0214: name = "Script Editor (root)";
0215: }
0216:
0217: // TODO: clean this up
0218: actionMap = initActions();
0219: hierarchy = initContext(isRootEditor);
0220: recorders = initRecorders();
0221: view = initFrame(isRootEditor);
0222: hierarchy.setFiltered(view, true);
0223: updateDynamicActions(view);
0224:
0225: view.setComponentBrowser(createComponentBrowser());
0226: addEventHandlers(view);
0227:
0228: // Clear the status only if there were no errors
0229: if (view.getStatus().equals(Strings.get("Initializing"))) {
0230: setStatus(Strings.get("Ready"));
0231: }
0232: rootIsExiting = false;
0233: }
0234:
0235: /** Provides a convenient menu setup definition.
0236: Use a defined action name to indicate that action's place within the
0237: menu. Null values indicate menu separators.
0238: */
0239: private String[][] initMenus() {
0240: ArrayList fileMenu = new ArrayList();
0241: ArrayList helpMenu = new ArrayList();
0242: fileMenu.addAll(Arrays.asList(new String[] { MENU_FILE,
0243: ACTION_SCRIPT_NEW, ACTION_SCRIPT_DUPLICATE,
0244: ACTION_SCRIPT_OPEN, null, ACTION_SCRIPT_SAVE,
0245: ACTION_SCRIPT_SAVE_AS, ACTION_SCRIPT_RENAME,
0246: ACTION_SCRIPT_CLOSE, null, ACTION_SCRIPT_DELETE, }));
0247:
0248: helpMenu.add(MENU_HELP);
0249: if (!Platform.isOSX()) {
0250: fileMenu.add(null);
0251: fileMenu.add(ACTION_EDITOR_QUIT);
0252: helpMenu.add(ACTION_EDITOR_ABOUT);
0253: }
0254: helpMenu.addAll(Arrays.asList(new String[] {
0255: ACTION_EDITOR_USERGUIDE, ACTION_EDITOR_WEBSITE,
0256: ACTION_EDITOR_EMAIL, ACTION_EDITOR_BUGREPORT, }));
0257:
0258: return new String[][] {
0259: (String[]) fileMenu
0260: .toArray(new String[fileMenu.size()]),
0261: { MENU_EDIT, ACTION_STEP_CUT, null,
0262: ACTION_STEP_MOVE_UP, ACTION_STEP_MOVE_DOWN,
0263: ACTION_STEP_GROUP, null,
0264: ACTION_SELECT_COMPONENT, null,
0265: ACTION_SCRIPT_CLEAR, },
0266: { MENU_TEST, ACTION_RUN, ACTION_RUN_TO,
0267: ACTION_RUN_SELECTED, null,
0268: ACTION_EXPORT_HIERARCHY, null,
0269: ACTION_RUN_LAUNCH, ACTION_RUN_TERMINATE, null,
0270: ACTION_TOGGLE_STOP_ON_FAILURE,
0271: ACTION_TOGGLE_STOP_ON_ERROR,
0272: ACTION_TOGGLE_FORKED, ACTION_GET_VMARGS,
0273: ACTION_TOGGLE_SLOW_PLAYBACK,
0274: ACTION_TOGGLE_AWT_MODE, },
0275: { MENU_INSERT, ACTION_INSERT_ANNOTATION,
0276: ACTION_INSERT_APPLET, ACTION_INSERT_CALL,
0277: ACTION_INSERT_COMMENT,
0278: ACTION_INSERT_EXPRESSION, ACTION_INSERT_LAUNCH,
0279: ACTION_INSERT_FIXTURE, ACTION_INSERT_SAMPLE,
0280: ACTION_INSERT_SCRIPT, ACTION_INSERT_SEQUENCE,
0281: ACTION_INSERT_TERMINATE, },
0282: { MENU_CAPTURE, },
0283: (String[]) helpMenu
0284: .toArray(new String[helpMenu.size()]), };
0285: }
0286:
0287: // All editor actions should be defined here
0288: private ActionMap initActions() {
0289: javax.swing.Action[] actions = { new EditorAboutAction(),
0290: new EditorEmailAction(), new EditorBugReportAction(),
0291: new EditorWebsiteAction(), new EditorUserGuideAction(),
0292: new EditorQuitAction(), new ScriptOpenAction(),
0293: new ScriptNewAction(), new ScriptDuplicateAction(),
0294: new ScriptSaveAction(), new ScriptSaveAsAction(),
0295: new ScriptRenameAction(), new ScriptCloseAction(),
0296: new ScriptDeleteAction(), new ScriptClearAction(),
0297: new StepCutAction(), new StepMoveUpAction(),
0298: new StepMoveDownAction(), new StepGroupAction(),
0299: new RunAction(), new RunToAction(),
0300: new RunSelectedAction(), new RunLaunchAction(),
0301: new RunTerminateAction(), new GetVMArgsAction(),
0302: new SelectTestSuiteAction(),
0303: new ExportHierarchyAction(), new ToggleForkedAction(),
0304: new InsertLaunchAction(), new InsertFixtureAction(),
0305: new InsertAppletAction(), new InsertTerminateAction(),
0306: new InsertCallAction(), new InsertSampleAction(),
0307: new InsertSequenceAction(), new InsertScriptAction(),
0308: new InsertCommentAction(),
0309: new InsertExpressionAction(),
0310: new InsertAnnotationAction(),
0311: new ToggleStopOnFailureAction(),
0312: new ToggleStopOnErrorAction(),
0313: new ToggleSlowPlaybackAction(),
0314: new ToggleAWTModeAction(), new CaptureImageAction(),
0315: new CaptureComponentAction(),
0316: new SelectComponentAction(), };
0317: ActionMap map = new ActionMap();
0318: for (int i = 0; i < actions.length; i++) {
0319: Object key = actions[i].getValue(EditorAction.ACTION_KEY);
0320: map.put(key, actions[i]);
0321: }
0322: return map;
0323: }
0324:
0325: /**
0326: * Add event handlers to their respective components
0327: */
0328: private void addEventHandlers(final ScriptEditorFrame view) {
0329: scriptTable.getSelectionModel().addListSelectionListener(
0330: new ScriptTableSelectionHandler());
0331: scriptTable.addMouseListener(new MouseAdapter() {
0332: public void mouseClicked(MouseEvent me) {
0333: if ((me.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
0334: if (view.getEditor() == null)
0335: setStepEditor();
0336: }
0337: }
0338: });
0339: MouseListener ml = new MouseAdapter() {
0340: public void mouseClicked(MouseEvent me) {
0341: if ((me.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
0342: int size = scriptTable.getRowCount();
0343: scriptTable.clearSelection();
0344: scriptTable.setCursorLocation(size);
0345: }
0346: }
0347: };
0348: view.addMouseListener(ml);
0349: view.getTestScriptSelector().addItemListener(
0350: new ScriptSelectorItemHandler());
0351: view.addWindowListener(new WindowAdapter() {
0352: public void windowClosing(WindowEvent e) {
0353: quitApplication();
0354: }
0355: });
0356: if (Platform.isOSX()) {
0357: // Mac has it's own dedicated Quit/About menu items
0358: OSXAdapter.register(view,
0359: actionMap.get(ACTION_EDITOR_QUIT), actionMap
0360: .get(ACTION_EDITOR_ABOUT), null);
0361: }
0362: }
0363:
0364: /**
0365: * Determines if the editor is testing itself and initializes the
0366: * security manager accordingly.
0367: */
0368: private TestHierarchy initContext(boolean isRoot) {
0369: TestHierarchy hierarchy = editorConfiguration
0370: .getTestHierarchy();
0371: if (hierarchy == null) {
0372: hierarchy = new TestHierarchy() {
0373: private String desc = "Test hierarchy for " + name;
0374:
0375: public String toString() {
0376: return desc;
0377: }
0378: };
0379: }
0380:
0381: // eventually this should go away; all hierarchy usage should be
0382: // explicit.
0383: oldHierarchy = AWTHierarchy.getDefault();
0384: if (isRoot) {
0385: AWTHierarchy.setDefault(hierarchy);
0386: initSecurityManager();
0387: try {
0388: new EventExceptionHandler().install();
0389: } catch (Exception e) {
0390: Log.warn(e);
0391: }
0392: }
0393: return hierarchy;
0394: }
0395:
0396: private Recorder[] initRecorders() {
0397: // Use the editor as the resolver, since the actual resolver will
0398: // be the currently scoped script.
0399: Recorder[] recorders = new Recorder[] {
0400: new EventRecorder(this , false),
0401: new EventRecorder(this , true), };
0402:
0403: ActionListener recorderListener = new ActionListener() {
0404: public void actionPerformed(final ActionEvent event) {
0405: setStatus(event.getActionCommand());
0406: }
0407: };
0408: for (int i = 0; i < recorders.length; i++) {
0409: recorders[i].addActionListener(recorderListener);
0410: }
0411: return recorders;
0412: }
0413:
0414: /** Initialize the primary editor frame. */
0415: private ScriptEditorFrame initFrame(final boolean isRoot) {
0416:
0417: scriptModel = new ScriptModel() {
0418: public boolean isCellEditable(int row, int col) {
0419: return false;
0420: }
0421: };
0422: runner = new EditorStepRunner();
0423: runner.setTerminateOnError(false);
0424: runner.addStepListener(new StepListener() {
0425: public void stateChanged(StepEvent ev) {
0426: if (ignoreStepEvents)
0427: return;
0428: reflectScriptExecutionState(ev);
0429: }
0430: });
0431: // Customize the ScriptTable so we can vary the color of any given
0432: // step based on our last run status (of which the table itself should
0433: // be ignorant).
0434: scriptTable = new ScriptTable(scriptModel) {
0435: public Color getStepColor(Step step, boolean selected) {
0436: Color color = super .getStepColor(step, selected);
0437: // Make the stop step appear a different color
0438: if (step == stopStep) {
0439: Color stopColor = getSelectionBackground().darker();
0440: color = stopColor;
0441: }
0442: Throwable thr = runner.getError(step);
0443: if (thr != null) {
0444: Color tint = (thr instanceof AssertionFailedError) ? statusColor[ScriptEditor.FAILURE]
0445: : statusColor[ScriptEditor.ERROR];
0446: if (step instanceof Sequence) {
0447: color = color.brighter();
0448: }
0449: if (selected) {
0450: color = mixColors(color, tint);
0451: } else {
0452: color = tint;
0453: }
0454: }
0455: return color;
0456: }
0457: };
0458:
0459: // Override default "cut" action in table
0460: ActionMap amap = scriptTable.getActionMap();
0461: amap.put("cut", actionMap.get(ACTION_STEP_CUT));
0462: InputMap imap = scriptTable.getInputMap();
0463: imap
0464: .put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0),
0465: "toggle");
0466:
0467: // Only allow the script editor to dispose of the frame
0468: String prefFile = Preferences.PROPS_FILENAME;
0469: if (!isRoot) {
0470: prefFile += ".tmp";
0471: }
0472: Preferences prefs = new Preferences(prefFile);
0473: String title = Strings.get("ScriptEditor.title",
0474: new Object[] { "" });
0475: ScriptEditorFrame f = new ScriptEditorFrame(initMenus(),
0476: actionMap, this , title, scriptTable, prefs) {
0477: /** @deprecated Don't allow code under test to hide the editor. */
0478: public void hide() {
0479: if (hiding || isDisposeFromRootEditor()) {
0480: hiding = false;
0481: super .hide();
0482: }
0483: }
0484:
0485: public void dispose() {
0486: // Need to prevent arbitrary disposal by the code under test.
0487: // Only allow the dispose if one of the following is true:
0488: // a) we triggered it (exiting == true)
0489: // b) the root script editor is disposing us
0490: if (exiting || isDisposeFromRootEditor()) {
0491: super .dispose();
0492: }
0493: }
0494:
0495: public String getName() {
0496: String name = super .getName();
0497: if (isRoot)
0498: name += " (root)";
0499: return name;
0500: }
0501: };
0502: return f;
0503: }
0504:
0505: /** Provide a color that is a mix of the two given colors. */
0506: private Color mixColors(Color c1, Color c2) {
0507: return new Color((c1.getRed() + c2.getRed()) / 2, (c1
0508: .getGreen() + c2.getGreen()) / 2, (c1.getBlue() + c2
0509: .getBlue()) / 2);
0510: }
0511:
0512: /** Return whether the root editor disposed of this instance. */
0513: private boolean isDisposeFromRootEditor() {
0514: // FIXME cf how applets prevent disposal of embedded frame
0515: // AWTHierarchy surrounds disposal calls with the property
0516: // abbot.finder.disposal set to "true"
0517: return !isRootEditor
0518: && Boolean.getBoolean("abbot.finder.disposal");
0519: }
0520:
0521: private void createAsserts(ArrayList list, ComponentTester tester,
0522: boolean wait) {
0523: list.clear();
0524: Method[] methods = tester.getAssertMethods();
0525: for (int i = 0; i < methods.length; i++) {
0526: list.add(new TesterMethodAction(tester, methods[i], wait));
0527: }
0528: methods = tester.getComponentAssertMethods();
0529: if (list.size() != 0 && methods.length != 0)
0530: list.add(null);
0531: for (int i = 0; i < methods.length; i++) {
0532: list.add(new TesterMethodAction(tester, methods[i], wait));
0533: }
0534: methods = tester.getPropertyMethods();
0535: if (list.size() != 0 && methods.length != 0)
0536: list.add(null);
0537: for (int i = 0; i < methods.length; i++) {
0538: String name = methods[i].getName();
0539: if (name.startsWith("is"))
0540: name = name.substring(2);
0541: else if (name.startsWith("get") || name.startsWith("has"))
0542: name = name.substring(3);
0543: list.add(new TesterMethodAction(tester, methods[i], wait));
0544: }
0545: }
0546:
0547: /** Return a script step encapsulating an image comparison.
0548: * Assumes the script context is not null.
0549: */
0550: private Step captureComponentImage(Component comp) {
0551: Step step = null;
0552: ComponentTester tester = ComponentTester.getTester(comp);
0553: java.awt.image.BufferedImage img = tester.capture(comp,
0554: !(comp instanceof Window));
0555: try {
0556: // Save the image file relative to the current context
0557: ComponentReference ref = addComponent(comp);
0558: File scriptFile = ((Script) getResolverContext()).getFile();
0559: File newFile = new File(
0560: getResolverContext().getDirectory(), scriptFile
0561: .getName()
0562: + "-"
0563: + ref.getID()
0564: + ImageComparator.IMAGE_SUFFIX);
0565: int index = 1;
0566: while (newFile.exists()) {
0567: newFile = new File(getResolverContext().getDirectory(),
0568: scriptFile.getName() + "-" + ref.getID() + "-"
0569: + index++
0570: + ImageComparator.IMAGE_SUFFIX);
0571: }
0572: ImageComparator.writeImage(newFile, img);
0573: // Note that the pathname is saved relative to the script
0574: // context.
0575: step = new Assert(getResolverContext(), null,
0576: ComponentTester.class.getName(), "assertImage",
0577: new String[] { ref.getID(), newFile.getName(),
0578: "true" }, "true", false);
0579: } catch (IOException io) {
0580: Log.warn(io);
0581: }
0582: return step;
0583: }
0584:
0585: /** Start recording, launching the code under test if necessary. */
0586: private void startRecording(Recorder rec) {
0587: Log.debug("Starting recorder");
0588: boolean noWindows = countShowingWindows(null) == 0;
0589: boolean canLaunch = testScript != null
0590: && testScript.hasLaunch() && !isAppLaunched();
0591: if (noWindows && !canLaunch) {
0592: // Can't launch, and there are no windows to
0593: // record on, so show a warning
0594: view.showError(Strings.get("NoWindows.title"), Strings
0595: .get("NoWindows"));
0596: } else {
0597: if (recorder != null)
0598: stopRecording(true);
0599: Log.debug("Now recording with " + rec);
0600: recording = true;
0601: recorder = rec;
0602:
0603: setStatus("Please wait...");
0604: // Apply an input blocking mask to show the recorder is busy
0605: //waiter = new SpinningDialWaitIndicator(view);
0606: //waiter.setText("Recording...");
0607: // get us out of the way
0608: // FIXME this puts us WAY back on linux; this is only a
0609: // problem in that the status bar is often hidden
0610: // Maybe make a floating status while the recorder is
0611: // running.
0612: // Only go back if the app is already up, otherwise the
0613: // about-to-be-launched app is hidden.
0614: if (!noWindows)
0615: view.toBack();
0616: recorder.start();
0617:
0618: if (noWindows) {
0619: launch(false);
0620: }
0621: }
0622: }
0623:
0624: /** Stop recording and update the recorder actions' state. */
0625: private void stopRecording(boolean discardRecording) {
0626: Log.debug("Stopping recorder");
0627: recording = false;
0628: int type = INFO;
0629: String extended = null;
0630: String status = Strings
0631: .get(discardRecording ? "RecorderCanceled"
0632: : "RecorderFinished");
0633: try {
0634: recorder.terminate();
0635: } catch (RecordingFailedException e) {
0636: String msg = Strings.get("editor.recording.stop_failure");
0637: Throwable error = e.getReason() instanceof BugReport ? e
0638: .getReason() : new BugReport(msg, e.getReason());
0639: Log.log("Recording stop failure: " + error.toString());
0640: view.showWarning(msg);
0641: status = error.getMessage();
0642: extended = error.toString();
0643: type = ERROR;
0644: }
0645: try {
0646: if (!discardRecording) {
0647: Step step = recorder.getStep();
0648: // Ignore empty results
0649: if (!(step instanceof Sequence && ((Sequence) step)
0650: .size() == 0)) {
0651: addStep(step);
0652: }
0653: }
0654: } finally {
0655: recorder = null;
0656: if (waiter != null) {
0657: waiter.dispose();
0658: waiter = null;
0659: }
0660: }
0661: view.toFront();
0662: setStatus(status, extended, type);
0663: }
0664:
0665: private RecordAllAction recordAllAction;
0666: private RecordAllAction recordAllMotionAction;
0667:
0668: private void updateDynamicActions(ScriptEditorFrame view) {
0669: Class cls = selectedComponent == null ? Component.class
0670: : selectedComponent.getClass();
0671: ComponentTester tester = ComponentTester.getTester(cls);
0672: // assert submenu
0673: createAsserts(assertActions, tester, false);
0674: // wait submenu
0675: createAsserts(waitActions, tester, true);
0676: // insert submenu (only include one instance of each uniquely-named
0677: // method.
0678: insertActions.clear();
0679: Map map = new HashMap();
0680: Method[] methods = tester.getActions();
0681: for (int i = 0; i < methods.length; i++) {
0682: TesterMethodAction action = new TesterMethodAction(tester,
0683: methods[i], true);
0684: map.put(action.getName(), action);
0685: }
0686: methods = tester.getComponentActions();
0687: for (int i = 0; i < methods.length; i++) {
0688: TesterMethodAction action = new TesterMethodAction(tester,
0689: methods[i], true);
0690: map.put(action.getName(), action);
0691: }
0692: insertActions.addAll(map.values());
0693: // capture actions
0694:
0695: captureActions.clear();
0696: recordAllAction = new RecordAllAction(ACTION_CAPTURE,
0697: recorders[0], false);
0698: captureActions.add(recordAllAction);
0699: recordAllMotionAction = new RecordAllAction(ACTION_CAPTURE_ALL,
0700: recorders[1], true);
0701: captureActions.add(recordAllMotionAction);
0702: captureActions.add(null);
0703: captureActions.add(new CaptureImageAction());
0704: captureActions.add(new CaptureComponentAction());
0705:
0706: view.populateInsertMenu(insertActions);
0707: view.populateAssertMenu(assertActions);
0708: view.populateWaitMenu(waitActions);
0709: view.populateCaptureMenu(captureActions);
0710: }
0711:
0712: private void setSelected(String which, boolean select) {
0713: javax.swing.Action action = actionMap.get(which);
0714: if (action != null) {
0715: ((EditorToggleAction) action).setSelected(select);
0716: } else {
0717: Log.warn("Toggle action " + which + " is missing");
0718: }
0719: }
0720:
0721: private void setEnabled(String which, boolean enable) {
0722: javax.swing.Action action;
0723: if (which == ACTION_DYNAMIC) {
0724: ArrayList[] lists = new ArrayList[] { captureActions,
0725: waitActions, assertActions, insertActions };
0726: for (int i = 0; i < lists.length; i++) {
0727: Iterator iter = lists[i].iterator();
0728: while (iter.hasNext()) {
0729: action = (javax.swing.Action) iter.next();
0730: if (action != null)
0731: action.setEnabled(enable);
0732: }
0733: }
0734: } else {
0735: action = actionMap.get(which);
0736: if (action != null) {
0737: action.setEnabled(enable);
0738: } else {
0739: Log.warn("Action " + which + " is missing");
0740: }
0741: }
0742: }
0743:
0744: /**
0745: * Initalize the componentBrowser and listeners to the scriptTable
0746: */
0747: private ComponentBrowser createComponentBrowser() {
0748: ComponentBrowser cb = new ComponentBrowser(this , hierarchy);
0749: cb.setEnabled(false);
0750: cb.addSelectionListener(new ComponentBrowserListener() {
0751: public void selectionChanged(ComponentBrowser src,
0752: Component comp, ComponentReference ref) {
0753: setSelectedComponent(comp, ref);
0754: }
0755:
0756: public void propertyAction(ComponentBrowser src, Method m,
0757: Object value, boolean sample) {
0758: if (selectedComponent == null)
0759: return;
0760: addPropertyMethodCall(m, value, sample);
0761: }
0762: });
0763: return cb;
0764: }
0765:
0766: /**
0767: * Install a new security manager to prevent launched applications
0768: * from exiting the JVM. This is only a partial solution; ideally
0769: * we'd like to be able to kill all the launched app's threads, or force
0770: * an unload of the class and reload it.
0771: * Should only be installed once, in the root editor context.
0772: */
0773: private void initSecurityManager() {
0774: if (Boolean.getBoolean("abbot.no_security_manager"))
0775: return;
0776:
0777: securityManager = editorConfiguration.getSecurityManager();
0778: if (securityManager == null) {
0779: securityManager = new EditorSecurityManager();
0780: }
0781:
0782: try {
0783: oldSecurityManager = System.getSecurityManager();
0784: System.setSecurityManager(securityManager);
0785: } catch (Exception e) {
0786: oldSecurityManager = securityManager = null;
0787: Log.warn(e);
0788: }
0789: }
0790:
0791: /** Respond to various components. */
0792: public void actionPerformed(ActionEvent ev) {
0793: if (ev.getSource() == view.getTestScriptSelector()
0794: && !ignoreComboBox) {
0795: Script script = (Script) view.getTestScriptSelector()
0796: .getSelectedItem();
0797: if (script != testScript)
0798: setScript(script);
0799: } else if (ev.getSource() == view.getTestScriptDescription()) {
0800: if (testScript != null) {
0801: JTextField tf = view.getTestScriptDescription();
0802: String desc = tf.getText();
0803: if ("".equals(desc)) {
0804: String cmd = ev.getActionCommand();
0805: if (!TextField.isDocumentAction(cmd)) {
0806: tf.setText(testScript.getDefaultDescription());
0807: testScript.setDescription(null);
0808: }
0809: } else if (!desc.equals(testScript
0810: .getDefaultDescription())) {
0811: testScript.setDescription(desc);
0812: }
0813: }
0814: } else {
0815: Log.warn("Unrecognized event: " + ev.getActionCommand()
0816: + "(" + ev.getID() + ")");
0817: }
0818: }
0819:
0820: /** Remove the selected step. */
0821: private void cutSelection() {
0822: int row = scriptTable.getSelectedRow();
0823: if (row == -1) {
0824: Log.warn("Unexpected cut state");
0825: return;
0826: }
0827: scriptModel.removeSteps(scriptTable.getSelectedSteps());
0828: int count = scriptTable.getRowCount();
0829: if (count > 0) {
0830: if (row >= count)
0831: row = count - 1;
0832: scriptTable.setRowSelectionInterval(row, row);
0833: scriptTable.setCursorLocation(row + 1);
0834: }
0835: setActionsEnabledState();
0836: setStatus("");
0837: }
0838:
0839: private void moveSelectionUp() {
0840: scriptTable.moveUp();
0841: setActionsEnabledState();
0842: }
0843:
0844: /** Move the selected step down. */
0845: private void moveSelectionDown() {
0846: scriptTable.moveDown();
0847: setActionsEnabledState();
0848: }
0849:
0850: /** Put the current selection into a sequence. */
0851: private void groupSelection() {
0852: int row = scriptTable.getSelectedRow();
0853: Sequence seq = new Sequence(getResolverContext(), (String) null);
0854: List list = scriptTable.getSelectedSteps();
0855: Step first = (Step) list.get(0);
0856: Sequence parent = scriptModel.getParent(first);
0857: int index = parent.indexOf(first);
0858: scriptModel.removeSteps(list);
0859: Iterator iter = list.iterator();
0860: Step last = parent;
0861: while (iter.hasNext()) {
0862: last = (Step) iter.next();
0863: seq.addStep(last);
0864: }
0865: scriptModel.insertStep(parent, seq, index);
0866: scriptModel.toggle(row);
0867: scriptTable.setRowSelectionInterval(row, scriptModel
0868: .getRowOf(last));
0869: setActionsEnabledState();
0870: }
0871:
0872: /** Insert a launch step. */
0873: void insertLaunch() {
0874: Step step = new Launch(getResolverContext(),
0875: LaunchEditor.HELP_DESC, "abbot.editor.ScriptEditor",
0876: "main", new String[] { "[]" }, ".", false);
0877: addStep(step);
0878: }
0879:
0880: /** Insert an applet step. */
0881: void insertApplet() {
0882: Step step = new Appletviewer(getResolverContext(),
0883: AppletviewerEditor.HELP_DESC, "your.applet.class.here",
0884: new HashMap(), null, null, null);
0885: addStep(step);
0886: }
0887:
0888: /** Insert a terminate step. */
0889: void insertTerminate() {
0890: Step step = new Terminate(getResolverContext(), (String) null);
0891: scriptTable.setCursorLocation(scriptTable.getRowCount());
0892: addStep(step);
0893: }
0894:
0895: private void insertCall(boolean sample) {
0896: if (sample) {
0897: addStep(new Sample(getResolverContext(), (String) null,
0898: Strings.get("YourClassName"), Strings
0899: .get("YourMethodName"), null, Strings
0900: .get("YourPropertyName")));
0901: } else {
0902: addStep(new Call(getResolverContext(), (String) null,
0903: Strings.get("YourClassName"), Strings
0904: .get("YourMethodName"), null));
0905: }
0906: }
0907:
0908: /** Insert a new, empty sequence. */
0909: private void insertSequence() {
0910: addStep(new Sequence(getResolverContext(), (String) null, null));
0911: }
0912:
0913: private void insertComment() {
0914: addStep(new Comment(getResolverContext(), ""));
0915: }
0916:
0917: private void insertExpression() {
0918: addStep(new Expression(getResolverContext(), ""));
0919: }
0920:
0921: private void insertAnnotation() {
0922: addStep(new Annotation(getResolverContext(), ""));
0923: }
0924:
0925: /** Insert another script as a step in this one. */
0926: private void insertScript(boolean fixture) {
0927: JFileChooser chooser = getChooser(filter);
0928: chooser.setCurrentDirectory(getWorkingDirectory());
0929: if (chooser.showOpenDialog(view) == JFileChooser.APPROVE_OPTION) {
0930: File file = chooser.getSelectedFile();
0931: if (!file.exists()) {
0932: try {
0933: file.createNewFile();
0934: } catch (IOException e) {
0935: view.showError(e.toString());
0936: }
0937: }
0938: Script script = fixture ? new Fixture(file
0939: .getAbsolutePath(), hierarchy) : new Script(file
0940: .getAbsolutePath(), hierarchy);
0941: try {
0942: script.load();
0943: addStep(script);
0944: } catch (Exception exc) {
0945: view.showError(exc.toString());
0946: }
0947: }
0948: }
0949:
0950: /** Returns the current test suite's directory, if available, the
0951: directory of the current script, if available, or the current working
0952: directory. If the current script has not yet been saved,
0953: uses the current working directory.
0954: */
0955: private File getWorkingDirectory() {
0956: return testSuite != null ? testSuite.getDirectory()
0957: : (testScript != null && !editingTempFile() ? testScript
0958: .getDirectory()
0959: : new File(System.getProperty("user.dir")));
0960: }
0961:
0962: /** Return a file chooser that filters for test scripts. */
0963: // FIXME some open, close operations should be sticky w/r/t
0964: // last directory used
0965: private JFileChooser getChooser(FileFilter f) {
0966: if (chooser == null) {
0967: chooser = new JFileChooser();
0968: chooser.setCurrentDirectory(getWorkingDirectory());
0969: }
0970: chooser.setFileFilter(f);
0971: return chooser;
0972: }
0973:
0974: /** Set the test case to the one corresponding to the given index. */
0975: private void setScript(int index) {
0976: if (getScripts().size() == 0) {
0977: setScript((Script) null);
0978: if (testSuite != null)
0979: setStatus(Strings.get("NoScripts"), null, WARN);
0980:
0981: } else {
0982: if (index >= getScripts().size())
0983: index = getScripts().size() - 1;
0984: setScript(getScriptAt(index));
0985: }
0986: }
0987:
0988: private void setScript(Script script) {
0989: if (script == testScript && script != null)
0990: return;
0991:
0992: Log.debug("Setting script to '" + script + "'");
0993: if (script != null) {
0994: try {
0995: script.load();
0996: } catch (InvalidScriptException ise) {
0997: Log.warn(ise);
0998: setScript((String) null);
0999: view.showError("Invalid Script", ise.toString());
1000: return;
1001: } catch (Exception e) {
1002: setScript((String) null);
1003: Log.warn(e);
1004: return;
1005: }
1006: }
1007:
1008: if (testScript != null) {
1009: UIContext context = testScript.getUIContext();
1010: if (context != null
1011: && !context.equivalent(runner.getCurrentContext()))
1012: runner.terminate();
1013: }
1014: testScript = script;
1015: scriptTable.clearSelection();
1016: scriptModel.setScript(script);
1017: if (script == null) {
1018: scriptTable.setCursorLocation(0);
1019: scriptTable.setEnabled(false);
1020: view.getTestScriptDescription().setText("");
1021: view.getTestScriptDescription().setEnabled(false);
1022: setStatus(Strings.get("NoScript"));
1023: } else {
1024: scriptTable.setEnabled(true);
1025: scriptTable.setCursorLocation(script.hasLaunch() ? 1 : 0);
1026: if (chooser != null) {
1027: chooser.setCurrentDirectory(script.getDirectory());
1028: }
1029: view.getTestScriptDescription().setText(
1030: script.getDescription());
1031: view.getTestScriptDescription().setEnabled(true);
1032: setStatus(Strings.get("editor.editing_script",
1033: new Object[] { testScript.getName() }));
1034: }
1035: setActionsEnabledState();
1036:
1037: ignoreComboBox = true;
1038: view.getTestScriptSelector().setSelectedItem(testScript);
1039: ignoreComboBox = false;
1040: updateTitle();
1041: }
1042:
1043: /** Update the state of all actions. This method should be invoked after
1044: * any GUI state change.
1045: */
1046: private void setActionsEnabledState() {
1047: if (!SwingUtilities.isEventDispatchThread()) {
1048: SwingUtilities.invokeLater(new Runnable() {
1049: public void run() {
1050: setActionsEnabledState();
1051: }
1052: });
1053: return;
1054: }
1055:
1056: boolean haveScript = testScript != null;
1057: boolean haveSelection = scriptTable.getSelectedRow() != -1;
1058: boolean notEmdedded = !editorConfiguration.isEmbedded();
1059:
1060: setEnabled(ACTION_SCRIPT_OPEN, true);
1061: setEnabled(ACTION_TOGGLE_STOP_ON_FAILURE, haveScript);
1062: setSelected(ACTION_TOGGLE_STOP_ON_FAILURE, haveScript
1063: && runner.getStopOnFailure());
1064: setEnabled(ACTION_TOGGLE_STOP_ON_ERROR, haveScript);
1065: setSelected(ACTION_TOGGLE_STOP_ON_ERROR, haveScript
1066: && runner.getStopOnError());
1067: setEnabled(ACTION_TOGGLE_FORKED, haveScript);
1068: setSelected(ACTION_TOGGLE_FORKED, haveScript
1069: && testScript.isForked());
1070: setEnabled(ACTION_GET_VMARGS, haveScript
1071: && testScript.isForked());
1072: setEnabled(ACTION_TOGGLE_SLOW_PLAYBACK, haveScript);
1073: setSelected(ACTION_TOGGLE_SLOW_PLAYBACK, haveScript
1074: && testScript.isSlowPlayback());
1075: setEnabled(ACTION_TOGGLE_AWT_MODE, haveScript);
1076: setSelected(ACTION_TOGGLE_AWT_MODE, haveScript
1077: && testScript.isAWTMode());
1078:
1079: setEnabled(ACTION_RUN, haveScript);
1080: setEnabled(ACTION_RUN_TO, haveScript && haveSelection);
1081: setEnabled(ACTION_RUN_SELECTED, haveScript && haveSelection
1082: && isAppLaunched());
1083: setEnabled(ACTION_EXPORT_HIERARCHY, haveScript
1084: && isAppLaunched());
1085: setEnabled(ACTION_RUN_LAUNCH, haveScript && notEmdedded
1086: && testScript.hasLaunch() && !isAppLaunched());
1087: setEnabled(ACTION_RUN_TERMINATE, isAppLaunched() && notEmdedded);
1088: setEnabled(ACTION_SCRIPT_NEW, true);
1089: setEnabled(ACTION_SCRIPT_DUPLICATE, haveScript);
1090: setEnabled(ACTION_SCRIPT_SAVE, haveScript);
1091: setEnabled(ACTION_SCRIPT_SAVE_AS, haveScript);
1092: setEnabled(ACTION_SCRIPT_RENAME, haveScript);
1093: setEnabled(ACTION_SCRIPT_DELETE, haveScript);
1094: setEnabled(ACTION_SCRIPT_CLOSE, haveScript);
1095:
1096: setEnabled(ACTION_STEP_CUT, haveScript && haveSelection);
1097: setEnabled(ACTION_STEP_MOVE_UP, haveScript && haveSelection
1098: && scriptTable.canMoveUp());
1099: setEnabled(ACTION_STEP_MOVE_DOWN, haveScript && haveSelection
1100: && scriptTable.canMoveDown());
1101: setEnabled(ACTION_STEP_GROUP, haveScript && haveSelection);
1102: setEnabled(ACTION_SCRIPT_CLEAR, haveScript);
1103:
1104: setEnabled(ACTION_INSERT_LAUNCH, notEmdedded && haveScript
1105: && !testScript.hasLaunch());
1106: setEnabled(ACTION_INSERT_FIXTURE, haveScript
1107: && !testScript.hasLaunch());
1108: setEnabled(ACTION_INSERT_APPLET, notEmdedded && haveScript
1109: && !testScript.hasLaunch());
1110: setEnabled(ACTION_INSERT_TERMINATE, notEmdedded && haveScript
1111: && !testScript.hasTerminate());
1112: setEnabled(ACTION_INSERT_SCRIPT, haveScript);
1113: setEnabled(ACTION_INSERT_CALL, haveScript);
1114: setEnabled(ACTION_INSERT_SAMPLE, haveScript);
1115: setEnabled(ACTION_INSERT_SEQUENCE, haveScript);
1116: setEnabled(ACTION_INSERT_COMMENT, haveScript);
1117: setEnabled(ACTION_INSERT_EXPRESSION, haveScript);
1118: setEnabled(ACTION_INSERT_ANNOTATION, haveScript);
1119: setEnabled(ACTION_DYNAMIC, haveScript);
1120:
1121: view.getComponentBrowser().setEnabled(!isScriptRunning);
1122: }
1123:
1124: /** Set the current test script. */
1125: void setScript(String filename) {
1126: Script script = filename != null ? new Script(filename,
1127: hierarchy) : null;
1128: setScript(script);
1129: }
1130:
1131: /** Indicate the component and/or reference currently in use. */
1132: private void setSelectedComponent(Component c,
1133: ComponentReference ref) {
1134:
1135: if (c == selectedComponent && ref == selectedReference)
1136: return;
1137:
1138: boolean updateActions = c != selectedComponent;
1139: selectedComponent = c;
1140: selectedReference = ref;
1141: String status;
1142: if (ref != null) {
1143: status = Strings.get(c == null ? "ComponentReferenceX"
1144: : "ComponentReference",
1145: new Object[] { ref.getID() });
1146: } else if (c != null) {
1147: status = hierarchy.contains(c) ? Strings
1148: .get("UnreferencedComponent") : Strings
1149: .get("editor.component_filtered");
1150: } else {
1151: status = Strings.get("NoComponent");
1152: }
1153: setStatus(status);
1154: if (updateActions) {
1155: updateDynamicActions(view);
1156: }
1157: }
1158:
1159: private void setTestSuite(String suiteClassname) {
1160: setTestSuite(suiteClassname, null);
1161: }
1162:
1163: /** Sets the currently selected test suite, updating all gui components
1164: appropriately. */
1165: private void setTestSuite(String suiteClassname, ClassLoader cl) {
1166: if (cl == null)
1167: cl = getClass().getClassLoader();
1168: Log.debug("Setting test suite to " + suiteClassname);
1169: testSuite = null;
1170: testScriptList = null;
1171: if (suiteClassname != null) {
1172: try {
1173: // FIXME use a dynamic class loader so we can reload after
1174: // changes to the suite/fixture class.
1175: Class cls = Class.forName(suiteClassname, true, cl);
1176: if (!ScriptFixture.class.isAssignableFrom(cls)
1177: && !ScriptTestSuite.class.isAssignableFrom(cls)) {
1178: view.showWarning(Strings.get("editor.wrong_class",
1179: new Object[] { cls.getName() }));
1180: } else {
1181: testClass = cls;
1182: Method suiteMethod = null;
1183: testSuite = null;
1184: try {
1185: suiteMethod = testClass.getMethod("suite",
1186: new Class[0]);
1187: testSuite = (ScriptTestSuite) suiteMethod
1188: .invoke(null, new Class[0]);
1189: } catch (NoSuchMethodException nsm) {
1190: view.showError(nsm.toString());
1191: testSuite = null;
1192: } catch (InvocationTargetException ite) {
1193: view.showError(ite.toString());
1194: } catch (IllegalAccessException iae) {
1195: view.showError(iae.toString());
1196: }
1197: }
1198: } catch (ClassNotFoundException e) {
1199: view.showWarning(Strings.get("editor.suite_not_found",
1200: new Object[] { suiteClassname }));
1201: }
1202: }
1203: if (testSuite == null) {
1204: view.getCurrentTestSuiteLabel().setText(
1205: Strings.get("NoSuite"));
1206: model = new DefaultComboBoxModel();
1207: view.getTestScriptSelector().setModel(model);
1208: view.getTestScriptSelector().setEnabled(false);
1209: model.setSelectedItem(null);
1210: } else {
1211: view.getTestScriptSelector().setEnabled(true);
1212: view.getCurrentTestSuiteLabel().setText(
1213: testSuite.toString());
1214: Object oldSelection = view.getTestScriptSelector()
1215: .getSelectedItem();
1216: ignoreComboBox = true;
1217: view.getTestScriptSelector().setEnabled(true);
1218: // Workaround for indexing bug on OSX
1219: view.getTestScriptSelector().setSelectedItem(null);
1220: List list = getScripts();
1221: Object[] data = list.toArray(new Object[list.size()]);
1222: model = new DefaultComboBoxModel(data);
1223: view.getTestScriptSelector().setModel(model);
1224: // If the test suite didn't actually change, then keep the old
1225: // selection.
1226: if (getScripts().contains(oldSelection))
1227: model.setSelectedItem(oldSelection);
1228: ignoreComboBox = false;
1229: }
1230: }
1231:
1232: /** Set the frame title to the default. */
1233: private void updateTitle() {
1234: String title = Strings.get("ScriptEditor.title",
1235: new Object[] { testScript != null ? (" ("
1236: + testScript.getName() + ")") : "" });
1237: view.setTitle(title);
1238: }
1239:
1240: /** Pull up a dialog with all available test suites. */
1241: private void browseTests() {
1242: ClassLoader cl = getContextClassLoader();
1243: String path = cl instanceof PathClassLoader ? ((PathClassLoader) cl)
1244: .getClassPath()
1245: : null;
1246: if (path == null)
1247: path = System.getProperty("java.class.path");
1248:
1249: TestSelector selector = new TestSelector(view, path);
1250: selector.setVisible(true);
1251: String className = selector.getSelectedItem();
1252: if (className != null && checkSaveBeforeClose()) {
1253: terminate();
1254: boolean none = className.equals(TestSelector.TEST_NONE);
1255: setTestSuite(none ? null : className, cl);
1256: setScript(0);
1257: if (none) {
1258: setStatus(Strings.get("editor.no_suite"));
1259: }
1260: }
1261: }
1262:
1263: /** @return true if it's ok to exit. */
1264: protected boolean checkSaveBeforeClose() {
1265: // query save/cancel/exit
1266: if (testScript != null && (testScript.isDirty())) {
1267: int opt = view.showConfirmation(Strings
1268: .get("ScriptModified"),
1269: JOptionPane.YES_NO_CANCEL_OPTION);
1270: if (opt == JOptionPane.CANCEL_OPTION
1271: || opt == JOptionPane.CLOSED_OPTION) {
1272: return false;
1273: } else if (opt == JOptionPane.YES_OPTION) {
1274: saveScript();
1275: }
1276: }
1277: return true;
1278: }
1279:
1280: private void closeScript() {
1281: if (!checkSaveBeforeClose())
1282: return;
1283: setScript((Script) null);
1284: }
1285:
1286: /** Quit the application. */
1287: void quitApplication() {
1288: if (!checkSaveBeforeClose())
1289: return;
1290:
1291: Log.debug("editor quit" + (isRootEditor ? " (root)" : ""));
1292: dispose();
1293: if (isRootEditor) {
1294: rootIsExiting = true;
1295: } else {
1296: AWTHierarchy.setDefault(oldHierarchy);
1297: }
1298:
1299: if (!editorConfiguration.isEmbedded()) {
1300: System.exit(0);
1301: }
1302: }
1303:
1304: /** Set the contents of the status message. */
1305: public void setStatus(String msg) {
1306: setStatus(msg, null, INFO);
1307: }
1308:
1309: /** Set the contents of the status message to the given exception. */
1310: private String getStackTrace(Throwable thr) {
1311: StringWriter writer = new StringWriter();
1312: thr.printStackTrace(new PrintWriter(writer));
1313: return writer.toString();
1314: }
1315:
1316: /** Set the contents of the status message. */
1317: public void setStatus(String msg, String extended, int type) {
1318: String text = Strings.get(statusFormat[type],
1319: new Object[] { msg });
1320: view.setStatus(text, extended, statusColor[type]);
1321: // Save all messages to the log
1322: Log.log(text);
1323: }
1324:
1325: private void setStatusForStep(Step step) {
1326: if (step == null) {
1327: setStatus("");
1328: return;
1329: }
1330:
1331: Throwable error = runner.getError(step);
1332: boolean fromScript = step == testScript;
1333: String msg = error != null ? error.toString() : null;
1334: String inStep = Strings.get("InStep", new Object[] { step });
1335: String where = Strings.get("StepAt",
1336: new Object[] { Script.getFile(step),
1337: new Integer(Script.getLine(step)) })
1338: + "\n";
1339: // Don't need location info for these
1340: if (error instanceof AssertionFailedError
1341: && ((AssertionFailedError) error).getLine() != 0) {
1342: where = "";
1343: }
1344:
1345: String extended = null;
1346: int type = INFO;
1347: if (error != null) {
1348: Log.log(error);
1349: boolean isFailure = (error instanceof AssertionFailedError);
1350: type = isFailure ? FAILURE : ERROR;
1351: extended = getStackTrace(error);
1352: // If we're not stopping on failure, don't mention it
1353: // specifically
1354: if (fromScript) {
1355: if ((isFailure && !runner.getStopOnFailure())
1356: || (!isFailure && !runner.getStopOnError())) {
1357: msg = Strings.get(isFailure ? "ScriptFailure"
1358: : "ScriptError");
1359: } else {
1360: // Otherwise ignore messages from the script itself
1361: return;
1362: }
1363: } else {
1364: extended = inStep + "\n" + where + extended;
1365: }
1366: } else if (fromScript) {
1367: msg = Strings.get("ScriptSuccess");
1368: }
1369: // If nothing interesting happened, leave the status alone
1370: if (msg != null)
1371: setStatus(msg, extended, type);
1372: }
1373:
1374: /** Return a ComponentReference corresponding to the currently selected
1375: Component or ComponentReference, creating one if necessary.
1376: */
1377: private String getSelectedComponentID() {
1378: String id = null;
1379: if (selectedReference != null) {
1380: id = selectedReference.getID();
1381: } else if (selectedComponent != null) {
1382: ComponentReference ref = addComponent(selectedComponent);
1383: id = ref.getID();
1384: setStatus(Strings.get("ComponentReference",
1385: new Object[] { id }));
1386: }
1387: return id;
1388: }
1389:
1390: /** Add either an Action or an Assert method provided by the given
1391: Tester. */
1392: private void addTesterCall(Method method, ComponentTester tester,
1393: boolean wait, String docs) {
1394: boolean invert = invertAssertions;
1395: Class[] params = method.getParameterTypes();
1396: String id = getSelectedComponentID();
1397: Class componentClass = selectedComponent != null ? selectedComponent
1398: .getClass()
1399: : null;
1400: boolean componentArg0 = params.length > 0
1401: && Component.class.isAssignableFrom(params[0]);
1402:
1403: String argString = view.showInputDialog(Strings
1404: .get("IdentifyArguments"), docs, componentArg0 ? id
1405: : null);
1406: if (argString == null)
1407: return;
1408:
1409: String[] args = ArgumentParser.parseArgumentList(argString);
1410: try {
1411: insertTesterCall(tester, method, componentClass, id, args,
1412: wait, invert);
1413: } catch (IllegalArgumentException iae) {
1414: Log.warn(iae);
1415: } catch (NoSuchReferenceException nsr) {
1416: Log.warn(nsr);
1417: }
1418: }
1419:
1420: /** Invoked after the user has selected a component and a property for
1421: * it.
1422: */
1423: private void addPropertyMethodCall(Method method, Object value,
1424: boolean sample) {
1425: String id = getSelectedComponentID();
1426: String methodName = method.getName();
1427: String[] args = ComponentTester.class.isAssignableFrom(method
1428: .getDeclaringClass()) ? new String[] { id } : null;
1429: String targetClassName = method.getDeclaringClass().getName();
1430: if (sample) {
1431: String varName = Strings.get("YourPropertyName");
1432: Sample step = args == null ? new Sample(
1433: getResolverContext(), null, methodName, id, varName)
1434: : new Sample(getResolverContext(), null,
1435: targetClassName, methodName, args, varName);
1436: addStep(step);
1437: } else {
1438: String expectedValue = ArgumentParser.toString(value);
1439: Assert step = args == null ? new Assert(
1440: getResolverContext(), null, methodName, id,
1441: expectedValue, invertAssertions) : new Assert(
1442: getResolverContext(), null, targetClassName,
1443: methodName, args, expectedValue, invertAssertions);
1444: step.setWait(waitAssertions);
1445: addStep(step);
1446: }
1447: }
1448:
1449: /** Returns null if not found. */
1450: public String getComponentID(Component comp) {
1451: ComponentReference ref = getComponentReference(comp);
1452: return ref != null ? ref.getID() : null;
1453: }
1454:
1455: /** Insert a new step at the current cursor location. */
1456: void addStep(Step step) {
1457: Sequence parent = scriptTable.getCursorParent();
1458: int index = scriptTable.getCursorParentIndex();
1459: scriptModel.insertStep(parent, step, index);
1460: int row = scriptModel.getRowOf(step);
1461: scriptTable.setRowSelectionInterval(row, row);
1462: scriptTable.setCursorLocation(row + 1);
1463: setActionsEnabledState();
1464: }
1465:
1466: /** Create a new script. */
1467: private void newScript(boolean copyFixture) {
1468: if (!checkSaveBeforeClose())
1469: return;
1470: File file = editorConfiguration.getNewFileTemplate();
1471: if (file == null) {
1472: try {
1473: file = File
1474: .createTempFile(Script.UNTITLED_FILE, ".xml");
1475: tempFile = file;
1476: } catch (IOException io) {
1477: JFileChooser chooser = getChooser(filter);
1478: File dir = getWorkingDirectory();
1479: chooser.setCurrentDirectory(dir);
1480: chooser.setSelectedFile(new File(dir,
1481: Script.UNTITLED_FILE + ".xml"));
1482: if (chooser.showSaveDialog(view) != JFileChooser.APPROVE_OPTION) {
1483: return;
1484: }
1485: file = chooser.getSelectedFile();
1486: }
1487: }
1488:
1489: if (!file.exists() || file.isFile()) {
1490: newScript(file, copyFixture);
1491: } else {
1492: // FIXME display an error
1493: }
1494: }
1495:
1496: /** Create a new script at the given filename, or open it if it already
1497: * exists. Optionally copies the fixture from the current script.
1498: */
1499: void newScript(File file, boolean copyFixture) {
1500: if (!checkSaveBeforeClose())
1501: return;
1502:
1503: try {
1504: boolean insert = file.createNewFile() || file.length() == 0;
1505: Script srcFixture = testScript;
1506: setScript(file.getAbsolutePath());
1507: if (insert) {
1508: if (!copyFixture || srcFixture == null) {
1509: insertTerminate();
1510: insertLaunch();
1511: } else {
1512: copyFixture(srcFixture);
1513: }
1514: }
1515: setStatus(Strings.get(copyFixture ? "FixtureDuplicated"
1516: : "NewScriptCreated",
1517: new Object[] { file.getName() }));
1518: } catch (IOException io) {
1519: view.showError("File Error", io.toString());
1520: }
1521: }
1522:
1523: /** Copy the fixture from the given script into the current one. */
1524: public void copyFixture(Script src) {
1525: Step first = src.getStep(0);
1526: Step last = src.getStep(src.size() - 1);
1527: if (first instanceof UIContext) {
1528: setStatus(Strings.get("editor.adding_launch"));
1529: addStep(first);
1530: }
1531: if (src.size() > 1 && (src.getStep(1) instanceof Assert)) {
1532: Assert wait = (Assert) src.getStep(1);
1533: if (wait.isWait()
1534: && wait.getMethodName()
1535: .equals("assertFrameShowing")) {
1536: setStatus(Strings.get("editor.adding_wait"));
1537: addStep(wait);
1538: }
1539: }
1540: if (last instanceof Terminate) {
1541: setStatus(Strings.get("editor.adding_terminate"));
1542: addStep(last);
1543: }
1544: }
1545:
1546: /** Launch the GUI under test. */
1547: private void launch(boolean terminateRecorder) {
1548: if (testScript == null) {
1549: Log.warn("null testScript");
1550: return;
1551: }
1552:
1553: ignoreStepEvents = true;
1554: invertAssertions = false;
1555: waitAssertions = false;
1556: view.setAssertOptions(waitAssertions, invertAssertions);
1557:
1558: // Clean up any extant windows
1559: if (terminateRecorder && recorder != null) {
1560: // Assume we want to keep the results
1561: stopRecording(false);
1562: }
1563:
1564: setStatus(Strings.get("Launching"));
1565: // FIXME time out and flag an error if the launch never returns
1566: // (even if threaded, it should return at some point)
1567: runSteps(testScript, true, Strings.get("LaunchingDone"),
1568: new Runnable() {
1569: public void run() {
1570: ignoreStepEvents = false;
1571: }
1572: });
1573: }
1574:
1575: /** Do everything we can to dispose of the application under test. */
1576: private void terminate() {
1577: if (recorder != null) {
1578: // Assume we want to keep the results
1579: stopRecording(false);
1580: }
1581: scriptTable.clearSelection();
1582: try {
1583: runner.terminate();
1584: } catch (Throwable e) {
1585: Log.warn(e);
1586: }
1587: }
1588:
1589: private void openScript() {
1590: if (!checkSaveBeforeClose())
1591: return;
1592: JFileChooser chooser = getChooser(filter);
1593: chooser.setCurrentDirectory(getWorkingDirectory());
1594: if (testScript != null) {
1595: chooser.setSelectedFile(testScript.getFile());
1596: }
1597: if (chooser.showOpenDialog(view) == JFileChooser.APPROVE_OPTION) {
1598: File file = chooser.getSelectedFile();
1599: setScript(file.getAbsolutePath());
1600: }
1601: if (!getScripts().contains(testScript) && model != null) {
1602: model.setSelectedItem(null);
1603: }
1604: }
1605:
1606: private void stopOnFailureToggle() {
1607: if (testScript != null) {
1608: runner.setStopOnFailure(!runner.getStopOnFailure());
1609: }
1610: }
1611:
1612: private void stopOnErrorToggle() {
1613: if (testScript != null) {
1614: runner.setStopOnError(!runner.getStopOnError());
1615: }
1616: }
1617:
1618: private void forkedToggle() {
1619: if (testScript != null) {
1620: testScript.setForked(!testScript.isForked());
1621: }
1622: setActionsEnabledState();
1623: }
1624:
1625: private void getVMArgs() {
1626: String args = view.showInputDialog(Strings
1627: .get("GetVMArgs.title"), Strings.get("GetVMArgs.msg"),
1628: testScript.getVMArgs());
1629: if (args != null) {
1630: testScript.setVMArgs(args);
1631: }
1632: }
1633:
1634: private void slowPlaybackToggle() {
1635: if (testScript != null) {
1636: testScript.setSlowPlayback(!testScript.isSlowPlayback());
1637: }
1638: }
1639:
1640: private void awtModeToggle() {
1641: if (testScript != null) {
1642: testScript.setAWTMode(!testScript.isAWTMode());
1643: }
1644: }
1645:
1646: private void runScript(Step stopAt) {
1647: if (testScript == null) {
1648: Log.warn("null testScript");
1649: return;
1650: }
1651: Log.debug("Running test case " + testScript);
1652:
1653: stopStep = stopAt;
1654: setStatus(Strings.get("actions.run.start"));
1655: Runnable completion = new Runnable() {
1656: public void run() {
1657: // When the script finishes, bring the main
1658: // app forward.
1659: view.toFront();
1660: }
1661: };
1662: runSteps(testScript, false, Strings.get("actions.run.finish"),
1663: completion);
1664: }
1665:
1666: private void exportHierarchy() {
1667: JFileChooser chooser = getChooser(null);
1668: if (chooser.showSaveDialog(view) == JFileChooser.APPROVE_OPTION) {
1669: setStatus(Strings.get("actions.export-hierarchy.start"));
1670: final File file = chooser.getSelectedFile();
1671: new Thread("Hierarchy Export") {
1672: public void run() {
1673: HierarchyWriter hw = new HierarchyWriter(hierarchy);
1674: try {
1675: FileWriter writer = new FileWriter(file);
1676: hw.writeHierarchy(writer);
1677: } catch (IOException io) {
1678: view.showError(Strings.get("SaveFailed.title"),
1679: io.toString());
1680: }
1681: setStatus(Strings
1682: .get("actions.export-hierarchy.finish"));
1683: }
1684: }.start();
1685: }
1686: }
1687:
1688: private void runSelectedSteps() {
1689: final List stepList = scriptTable.getSelectedSteps();
1690: if (testScript == null || stepList == null
1691: || stepList.size() == 0 || !isAppLaunched()) {
1692: Log.warn("inconsistent program state");
1693: return;
1694: }
1695:
1696: Sequence steps = new Sequence(getResolverContext(), null,
1697: stepList);
1698: final int row0 = scriptModel.getRowOf((Step) stepList.get(0));
1699: final int row1 = scriptModel.getRowOf((Step) stepList.get(steps
1700: .size() - 1));
1701: setStatus(Strings.get("actions.run-selected.start"));
1702: hideView();
1703: runSteps(steps, false, Strings
1704: .get("actions.run-selected.finish"), new Runnable() {
1705: public void run() {
1706: scriptTable.setRowSelectionInterval(row0, row1);
1707: view.setVisible(true);
1708: }
1709: });
1710: }
1711:
1712: /** Invoke the given test script. All test execution is done on a dedicated
1713: * thread/thread group to avoid interfering with the event dispatch thread.
1714: */
1715: private void runSteps(final Step which, final boolean launch,
1716: final String completionMessage, final Runnable onCompletion) {
1717: Log.debug("running " + which);
1718: setActionsEnabledState();
1719: lastLaunchTime = System.currentTimeMillis();
1720: //waiter = new SpinningDialWaitIndicator(view);
1721: //waiter.setText("Running...");
1722: Runnable action = new LaunchAction(which, onCompletion,
1723: completionMessage, launch);
1724: String groupName = "AUT Thread Group for " + this + ":"
1725: + nonce++;
1726: if (appGroup == null) {
1727: appGroup = new ThreadGroup(groupName) {
1728: public void uncaughtException(Thread t, Throwable thrown) {
1729: if (!(thrown instanceof ExitException)
1730: && !(thrown instanceof ThreadDeath)) {
1731: Log
1732: .warn("Application thread exception not caught: "
1733: + t);
1734: Log.warn(thrown);
1735: }
1736: }
1737: };
1738: }
1739: Thread launcher = new Thread(appGroup, action, "Script runner:"
1740: + nonce);
1741: launcher.setDaemon(true);
1742: view.getComponentBrowser().setEnabled(false);
1743: view.setEditor(null);
1744: isScriptRunning = true;
1745: launcher.start();
1746: }
1747:
1748: /** Remove all contents from the current script. */
1749: private void clearScript() {
1750: if (testScript == null) {
1751: Log.warn("null testScript");
1752: return;
1753: }
1754: if (view.showConfirmation(Strings
1755: .get("editor.confirm.clear_script")) == JOptionPane.YES_OPTION) {
1756: scriptTable.clearSelection();
1757: scriptTable.setCursorLocation(0);
1758: testScript.clear();
1759: scriptModel.setScript(testScript);
1760: }
1761: }
1762:
1763: /** Delete the currently selected script/test case. */
1764: private void deleteScript() {
1765: if (testScript == null) {
1766: Log.warn("null testScript");
1767: return;
1768: }
1769: if (view.showConfirmation(Strings
1770: .get("editor.confirm.delete_script")) == JOptionPane.YES_OPTION) {
1771: File file = testScript.getFile();
1772: int index = view.getTestScriptSelector().getSelectedIndex();
1773: file.delete();
1774: setTestSuite(testClass.getName(), testClass
1775: .getClassLoader());
1776: setScript(index);
1777: }
1778: }
1779:
1780: /** Change the file backing the current script/test case, renaming
1781: * its file if <code>rename</code> is true, or saving to a new destination
1782: * if not.
1783: */
1784: private void saveAsScript(boolean rename) {
1785: if (testScript == null) {
1786: Log.warn("null testScript");
1787: return;
1788: }
1789: File oldFile = testScript.getFile();
1790: JFileChooser chooser = getChooser(filter);
1791: chooser.setCurrentDirectory(getWorkingDirectory());
1792: // FIXME make sure it falls within the test suite's script set?
1793: //chooser.setFileFilter(testScript.getFileFilter());
1794: Log.debug("Showing save dialog");
1795: if (chooser.showSaveDialog(view) == JFileChooser.APPROVE_OPTION) {
1796: Log.debug("Accepted");
1797: File newFile = chooser.getSelectedFile();
1798: if (rename) {
1799: if (!oldFile.renameTo(newFile)) {
1800: view.showError(Strings.get(
1801: "editor.save.rename_failed", new Object[] {
1802: oldFile, newFile }));
1803: return;
1804: }
1805: }
1806: testScript.setFile(newFile);
1807: saveScript();
1808: updateTitle();
1809:
1810: if (testSuite != null && testSuite.accept(newFile)) {
1811: setTestSuite(testClass.getName(), testSuite.getClass()
1812: .getClassLoader());
1813: } else {
1814: setTestSuite(null);
1815: }
1816:
1817: if (rename) {
1818: setStatus(Strings.get("ScriptRename",
1819: new Object[] { newFile.getName() }));
1820: } else {
1821: setStatus(Strings.get("ScriptSaved",
1822: new Object[] { newFile.getName() }));
1823: }
1824: // Combo box doesn't know that the script has changed its
1825: // toString
1826: view.getTestScriptSelector().repaint();
1827:
1828: // Default script description reflects the path
1829: String text = testScript != null ? testScript
1830: .getDescription() : "";
1831: view.getTestScriptDescription().setText(text);
1832: }
1833: }
1834:
1835: private boolean editingTempFile() {
1836: return testScript != null
1837: && testScript.getFile().equals(tempFile);
1838: }
1839:
1840: /** Save the current script/test case state to disk. */
1841: private void saveScript() {
1842: if (testScript == null) {
1843: Log.warn("null testScript");
1844: return;
1845: }
1846: // If the file is a temporary file, prompt for its real location
1847: if (editingTempFile()) {
1848: // This will recurse back into saveScript, so we're done
1849: Log
1850: .debug("Directory is temporary directory, need to rename");
1851: saveAsScript(false);
1852: return;
1853: }
1854: File file = testScript.getFile();
1855: File parent = file.getParentFile();
1856:
1857: // Try to ensure that the file and parent are writable, these operations
1858: // might fail so we still might get a failure later on.
1859: //
1860:
1861: FileSystemHelper fileSystemHelper = editorConfiguration
1862: .getFileSystemHelper();
1863:
1864: //
1865:
1866: boolean canWrite = (!file.exists() && fileSystemHelper
1867: .makeWritable(parent))
1868: || (file.exists() && fileSystemHelper
1869: .makeWritable(file));
1870: if (!canWrite) {
1871: String msg = Strings.get("NoFilePermission",
1872: new Object[] { file.toString() });
1873: view.showError(Strings.get("SaveFailed.title"), msg);
1874: } else {
1875: try {
1876: setStatus(Strings.get("Saving", new Object[] { file }));
1877: saveNestedScripts(testScript);
1878: setStatus(Strings.get("Saved", new Object[] { file }));
1879: } catch (IOException exc) {
1880: view.showError(Strings.get("SaveFailed.title"), exc
1881: .toString());
1882: }
1883: }
1884: }
1885:
1886: void saveNestedScripts(Sequence seq) throws IOException {
1887: if (seq instanceof Script) {
1888: Script script = (Script) seq;
1889: File file = script.getFile();
1890:
1891: // Make sure that the file can be written to
1892: //
1893:
1894: if (!editorConfiguration.getFileSystemHelper()
1895: .makeWritable(file)) {
1896: throw new IOException(Strings.get("NoFilePermission",
1897: new Object[] { file.toString() }));
1898:
1899: }
1900:
1901: // Save the script
1902: script.save();
1903: }
1904:
1905: Iterator iter = seq.steps().iterator();
1906: while (iter.hasNext()) {
1907: Step step = (Step) iter.next();
1908: if (step instanceof Sequence) {
1909: saveNestedScripts((Sequence) step);
1910: }
1911: }
1912: }
1913:
1914: private EventNormalizer normalizer = new EventNormalizer();
1915:
1916: /** Start listening to GUI events. */
1917: private void startListening() {
1918: normalizer.startListening(new SingleThreadedEventListener() {
1919: protected void processEvent(AWTEvent event) {
1920: ScriptEditor.this .processEvent(event);
1921: }
1922: }, FIXTURE_EVENT_MASK);
1923: view.getComponentBrowser().setEnabled(true);
1924: }
1925:
1926: /** Return the number of windows that are showing. */
1927: private int countShowingWindows(Window root) {
1928: int count = root != null && root.isShowing() ? 1 : 0;
1929: Iterator iter = root == null ? hierarchy.getRoots().iterator()
1930: : hierarchy.getComponents(root).iterator();
1931: while (iter.hasNext()) {
1932: Component c = (Component) iter.next();
1933: if (c instanceof Window) {
1934: count += countShowingWindows((Window) c);
1935: }
1936: }
1937: return count;
1938: }
1939:
1940: private int DONT_CARE = -1;
1941:
1942: private boolean isKeyPress(AWTEvent event, int code, int modifiers) {
1943: return event.getID() == KeyEvent.KEY_PRESSED
1944: && ((KeyEvent) event).getKeyCode() == code
1945: && (((KeyEvent) event).getModifiers() == modifiers || modifiers == DONT_CARE);
1946: }
1947:
1948: private boolean isKeyRelease(AWTEvent event, int code, int modifiers) {
1949: return event.getID() == KeyEvent.KEY_RELEASED
1950: && ((KeyEvent) event).getKeyCode() == code
1951: && (((KeyEvent) event).getModifiers() == modifiers || modifiers == DONT_CARE);
1952: }
1953:
1954: /** The editor does many things with the event stream, including logging
1955: * events, passing them off to the recorder, and updating its internal
1956: * state.
1957: */
1958: private void processEvent(AWTEvent event) {
1959: Object src = event.getSource();
1960: boolean isComponent = src instanceof Component;
1961: boolean isFiltered = isComponent
1962: && hierarchy.isFiltered((Component) src);
1963: // Keep a log of all events we see on non-filtered components
1964: if (isRootEditor) {
1965: if ((LOG_ALL_EVENTS || (!isFiltered && !ignoreEvents))
1966: && Boolean.getBoolean("abbot.fixture.log_events")) {
1967: Log.log("ED: " + Robot.toString(event) + " ("
1968: + Thread.currentThread() + ")");
1969: }
1970: }
1971: // Allow only component events and AWT menu actions
1972: if (!isComponent && !(src instanceof MenuComponent)) {
1973: Log.warn("Source not a Component or MenuComponent: "
1974: + event);
1975: return;
1976: }
1977: // If the script is running (or even being launched), the code
1978: // under test may do things that should really be done on the event
1979: // dispatch thread (initial component show and such).
1980: // If this is the case, defer mucking about with AWT until the code
1981: // under test has stabilized.
1982: if (isScriptRunning) {
1983: return;
1984: }
1985:
1986: if (!handleEditorTransient(event)
1987: && !handleRecordingControl(event)
1988: && !handleImageCaptureControl(event)
1989: && !handleComponentSelection(event) && recorder != null
1990: && recording && !isFiltered) {
1991: Log.debug("recorder process event");
1992: try {
1993: recorder.record(event);
1994: } catch (RecordingFailedException e) {
1995: // Stop recording, but keep what we've got so far
1996: stopRecording(false);
1997: String msg = Strings.get("editor.recording.failure");
1998: Throwable error = e.getReason() instanceof BugReport ? e
1999: .getReason()
2000: : new BugReport(msg, e.getReason());
2001: Log.log("Recording failure: " + error.toString());
2002: setStatus(error.getMessage(), error.toString(), ERROR);
2003: view.showWarning(msg);
2004: }
2005: }
2006:
2007: updateComponents(event);
2008: }
2009:
2010: private boolean handleComponentSelection(AWTEvent event) {
2011: Component ultimateComponent = state.getUltimateMouseComponent();
2012: if (ultimateComponent != null
2013: && !hierarchy.isFiltered(ultimateComponent)) {
2014: boolean keySelect = isKeyPress(event, selectKey,
2015: KeyEvent.SHIFT_MASK);
2016: boolean mouseSelect = event.getID() == MouseEvent.MOUSE_PRESSED
2017: && AWT.isTertiaryButton(((MouseEvent) event)
2018: .getModifiers());
2019: boolean makeReference = isKeyPress(event, selectKey,
2020: KeyEvent.SHIFT_MASK | KeyEvent.ALT_MASK);
2021:
2022: if (keySelect || mouseSelect || makeReference) {
2023: Component selected = event instanceof MouseEvent ? InputState
2024: .getComponentAt((Component) event.getSource(),
2025: ((MouseEvent) event).getPoint())
2026: : ultimateComponent;
2027: selectComponent(selected, makeReference);
2028: return true;
2029: }
2030: }
2031: return false;
2032: }
2033:
2034: /** Update the state of components whose appearance depends on keyboard
2035: * state.
2036: * @param event
2037: */
2038: private void updateComponents(AWTEvent event) {
2039: // Adjust the state of the assert/sample options
2040: if (isKeyPress(event, KC_INVERT, DONT_CARE)) {
2041: invertAssertions = true;
2042: view.setAssertOptions(waitAssertions, invertAssertions);
2043: } else if (isKeyRelease(event, KC_INVERT, DONT_CARE)) {
2044: invertAssertions = false;
2045: view.setAssertOptions(waitAssertions, invertAssertions);
2046: } else if (isKeyPress(event, KC_WAIT, DONT_CARE)) {
2047: waitAssertions = true;
2048: view.setAssertOptions(waitAssertions, invertAssertions);
2049: } else if (isKeyRelease(event, KC_WAIT, DONT_CARE)) {
2050: waitAssertions = false;
2051: view.setAssertOptions(waitAssertions, invertAssertions);
2052: }
2053: }
2054:
2055: private boolean handleEditorTransient(AWTEvent event) {
2056: // Make sure we filter any transient windows generated by the
2057: // script editor's frame. This avoids some of the hierarchy event
2058: // NPEs present on pre-1.4 VMs.
2059: if (event.getID() == WindowEvent.WINDOW_OPENED
2060: && ((WindowEvent) event).getWindow().getParent() == view) {
2061: hierarchy.setFiltered(((WindowEvent) event).getWindow(),
2062: true);
2063: view.getComponentBrowser().refresh();
2064: return true;
2065: }
2066: return false;
2067: }
2068:
2069: private boolean handleImageCaptureControl(AWTEvent event) {
2070: Object src = event.getSource();
2071: boolean isComponent = event.getSource() instanceof Component;
2072: boolean isFiltered = isComponent
2073: && hierarchy.isFiltered((Component) event.getSource());
2074: Component ultimateComponent = state.getUltimateMouseComponent();
2075: if (capturingImage) {
2076: // Cancel an image capture on ESC
2077: if (isKeyRelease(event, KeyEvent.VK_ESCAPE, 0)) {
2078: imageCaptureCancel();
2079: } else if (captureComponent != null
2080: && isKeyPress(event, KeyEvent.VK_UP, 0)
2081: && !(src instanceof Window)) {
2082: imageCaptureSelect(true);
2083: } else if (captureComponent != null
2084: && isKeyPress(event, KeyEvent.VK_DOWN, 0)
2085: && !(src instanceof Window)) {
2086: imageCaptureSelect(false);
2087: } else if (isKeyRelease(event, captureImageKey,
2088: KeyEvent.SHIFT_MASK)
2089: && !isFiltered
2090: && testScript != null
2091: && ultimateComponent != null) {
2092: imageCapture();
2093: }
2094: return true;
2095: } else if (isKeyRelease(event, captureImageKey,
2096: KeyEvent.SHIFT_MASK)
2097: && !isFiltered
2098: && testScript != null
2099: && ultimateComponent != null) {
2100: imageCaptureStart(ultimateComponent);
2101: return true;
2102: }
2103: return false;
2104: }
2105:
2106: private boolean handleRecordingControl(AWTEvent e) {
2107: boolean editorActivated = e.getID() == WindowEvent.WINDOW_ACTIVATED
2108: && e.getSource() == view;
2109: boolean appGone = isAppLaunched()
2110: && countShowingWindows(null) == 0
2111: && (appGroup != null && appGroup.activeCount() == 0)
2112: && System.currentTimeMillis() - lastLaunchTime > 7500;
2113: if (appGone)
2114: Log.debug("Code under test no longer running");
2115: boolean isComponent = e.getSource() instanceof Component;
2116: boolean isFiltered = isComponent
2117: && hierarchy.isFiltered((Component) e.getSource());
2118:
2119: if (isKeyPress(e, captureKey, KeyEvent.SHIFT_MASK)
2120: || editorActivated || appGone) {
2121: Log.debug("stop recording trigger");
2122: if (recording) {
2123: stopRecording(false);
2124: justStoppedRecording = true;
2125: if (capturingImage)
2126: imageCaptureCancel();
2127: } else {
2128: justStoppedRecording = false;
2129: }
2130: return true;
2131: }
2132: // Start recording all events on alt+shift+F2
2133: else if (isKeyRelease(e, captureKey, KeyEvent.SHIFT_MASK
2134: | KeyEvent.ALT_MASK)) {
2135: Log.debug("start recording trigger");
2136: if (!isFiltered && !recording && !justStoppedRecording) {
2137: Log.debug("Start recording events (+motion)");
2138: startRecording(recorders[1]);
2139: return true;
2140: }
2141: }
2142: // Start recording on shift+F2
2143: else if (isKeyRelease(e, captureKey, KeyEvent.SHIFT_MASK)) {
2144: Log.debug("start recording trigger");
2145: if (!isFiltered && !recording && !justStoppedRecording) {
2146: Log.debug("Start recording events");
2147: startRecording(recorders[0]);
2148: return true;
2149: }
2150: }
2151: return false;
2152: }
2153:
2154: private void selectComponent(Component c, boolean makeReference) {
2155: Log.debug("Selected: " + Robot.toString(c));
2156: // We usually want the combo box itself, not its LAF button
2157: if (c != null && (c.getParent() instanceof JComboBox))
2158: c = c.getParent();
2159: if (makeReference && getResolverContext() != null) {
2160: getResolverContext().addComponent(c);
2161: // FIXME tell the reference browser that the list of references
2162: // has changed.
2163: }
2164: view.getComponentBrowser().setSelectedComponent(c);
2165: }
2166:
2167: private void imageCaptureStart(Component ultimateComponent) {
2168: Log.debug("image capture locate");
2169: recording = false;
2170: capturingImage = true;
2171: captureComponent = ultimateComponent;
2172: innermostCaptureComponent = captureComponent;
2173: if (captureComponent instanceof JComponent) {
2174: highlighter = new Highlighter((JComponent) captureComponent);
2175: }
2176: setStatus("Image capture target is "
2177: + Robot.toString(captureComponent));
2178: }
2179:
2180: private void imageCaptureCancel() {
2181: Log.debug("stop image capture command");
2182: highlighter.dispose();
2183: setStatus("Image capture canceled");
2184: captureComponent = innermostCaptureComponent = null;
2185: capturingImage = false;
2186: }
2187:
2188: private void imageCaptureSelect(boolean up) {
2189: if (up) {
2190: Log.debug("image capture move up");
2191: Component parent = captureComponent.getParent();
2192: if (parent instanceof JComponent
2193: && !(captureComponent instanceof JLayeredPane)) {
2194: Log.debug("Changing from "
2195: + Robot.toString(captureComponent) + " to "
2196: + Robot.toString(parent));
2197: highlighter.dispose();
2198: highlighter = new Highlighter((JComponent) parent);
2199: captureComponent = parent;
2200: }
2201: } else {
2202: Log.debug("image capture move down");
2203: if (captureComponent instanceof Container) {
2204: Component[] subs = ((Container) captureComponent)
2205: .getComponents();
2206: for (int i = 0; i < subs.length; i++) {
2207: if (SwingUtilities.isDescendingFrom(
2208: innermostCaptureComponent, subs[i])
2209: && subs[i] instanceof JComponent) {
2210: Log.debug("Changing from "
2211: + Robot.toString(captureComponent)
2212: + " to " + Robot.toString(subs[i]));
2213: highlighter.dispose();
2214: highlighter = new Highlighter(
2215: (JComponent) subs[i]);
2216: captureComponent = subs[i];
2217: break;
2218: }
2219: }
2220: }
2221: }
2222: setStatus("Image capture target is "
2223: + Robot.toString(captureComponent));
2224: }
2225:
2226: private void imageCapture() {
2227: Log.debug("image capture snapshot");
2228: setStatus("Capturing image...");
2229: view.repaint();
2230: highlighter.dispose();
2231: // Must wait for the highlight to go away
2232: new Thread("wait for repaint") {
2233: public void run() {
2234: while (Toolkit.getDefaultToolkit()
2235: .getSystemEventQueue().peekEvent() != null) {
2236: try {
2237: sleep(10);
2238: } catch (InterruptedException e) {
2239: }
2240: }
2241: SwingUtilities.invokeLater(new Runnable() {
2242: public void run() {
2243: Step step = captureComponentImage(captureComponent);
2244: if (step != null) {
2245: // If we capture while recording, add it to the
2246: // recorder's stream. Otherwise, add it directly.
2247: if (recorder != null) {
2248: recorder.insertStep(step);
2249: } else {
2250: addStep(step);
2251: }
2252: }
2253: setStatus("Capturing image...done");
2254: captureComponent = innermostCaptureComponent = null;
2255: capturingImage = false;
2256: }
2257: });
2258: }
2259: }.start();
2260: }
2261:
2262: /** Return a List of script filenames in the current suite. */
2263: private List getScripts() {
2264: if (testScriptList == null) {
2265: testScriptList = getScripts(testSuite);
2266: }
2267: return testScriptList;
2268: }
2269:
2270: /** Return a List of script filenames contained in the given Test. */
2271: private List getScripts(junit.framework.Test node) {
2272: ArrayList names = new ArrayList();
2273: if (node == null) {
2274: } else if (node instanceof ScriptFixture) {
2275: names.add(((ScriptFixture) node).getName());
2276: } else if (node instanceof junit.framework.TestSuite) {
2277: Enumeration e = ((junit.framework.TestSuite) node).tests();
2278: while (e.hasMoreElements()) {
2279: junit.framework.Test test = (junit.framework.Test) e
2280: .nextElement();
2281: names.addAll(getScripts(test));
2282: }
2283: } else if (node instanceof junit.extensions.TestDecorator) {
2284: junit.framework.Test base = ((junit.extensions.TestDecorator) node)
2285: .getTest();
2286: names.addAll(getScripts(base));
2287: }
2288: //Log.debug("Test scripts under " + node + ": " + names.size());
2289: return names;
2290: }
2291:
2292: /** Returns the test case at the given index. */
2293: private Script getScriptAt(int index) {
2294: List filenames = getScripts();
2295: if (index >= filenames.size())
2296: index = filenames.size() - 1;
2297: return new Script((String) filenames.get(index), hierarchy);
2298: }
2299:
2300: /** Create a new step and insert it at the cursor. */
2301: private void insertTesterCall(ComponentTester tester,
2302: Method method, Class componentClass, String id,
2303: String[] argList, boolean wait, boolean invert)
2304: throws NoSuchReferenceException {
2305: String expectedResult = "true";
2306: String methodName = method.getName();
2307:
2308: if (methodName.startsWith("assert")) {
2309: Assert step;
2310: if (id == null) {
2311: // Built-in ComponentTester assertion
2312: step = new Assert(getResolverContext(), null, method
2313: .getDeclaringClass().getName(), methodName,
2314: argList, expectedResult, invert);
2315: } else {
2316: // Property method on a component
2317: ComponentReference ref = getComponentReference(id);
2318: if (ref == null)
2319: throw new NoSuchReferenceException(id);
2320: step = new Assert(getResolverContext(), null,
2321: methodName, argList,
2322: method.getDeclaringClass(), expectedResult,
2323: invert);
2324: }
2325: step.setWait(wait);
2326: addStep(step);
2327: } else if (methodName.startsWith("action")) {
2328: if (id == null) {
2329: // non-component action
2330: addStep(new Action(getResolverContext(), null,
2331: methodName, argList));
2332: } else {
2333: ComponentReference ref = getComponentReference(id);
2334: if (ref == null)
2335: throw new NoSuchReferenceException(id);
2336: addStep(new Action(getResolverContext(), null,
2337: methodName, argList, method.getDeclaringClass()));
2338: }
2339: } else {
2340: // It's an tester-provided property method
2341: ComponentReference ref = getComponentReference(id);
2342: if (ref == null)
2343: throw new NoSuchReferenceException(id);
2344: addStep(new Assert(getResolverContext(), null, methodName,
2345: argList, tester.getTestedClass(componentClass),
2346: expectedResult, invert));
2347: }
2348: }
2349:
2350: /** Update the UI to reflect the current state of the script's
2351: * execution.
2352: */
2353: private void reflectScriptExecutionState(StepEvent ev) {
2354: Step step = ev.getStep();
2355: String cmd = ev.getType();
2356: Log.debug("Got step event " + ev);
2357: if (cmd.equals(StepEvent.STEP_START)) {
2358: if (step == stopStep) {
2359: runner.stop();
2360: stopStep = null;
2361: }
2362: if (step != testScript) {
2363: final int row = scriptModel.getRowOf(step);
2364: if (row == -1) {
2365: // Step not visible, ignore
2366: } else {
2367: SwingUtilities.invokeLater(new Runnable() {
2368: public void run() {
2369: scriptTable.setRowSelectionInterval(row,
2370: row);
2371: scriptTable.setCursorLocation(row + 1);
2372: Rectangle rect = scriptTable.getCellRect(
2373: row, 0, true);
2374: scriptTable.scrollRectToVisible(rect);
2375: }
2376: });
2377: }
2378: int i = testScript.indexOf(step);
2379: if (i != -1) {
2380: setStatus(Strings.get("RunningStep", new Object[] {
2381: String.valueOf(i + 1),
2382: String.valueOf(testScript.size()) }));
2383: }
2384: }
2385: } else if (cmd.equals(StepEvent.STEP_FAILURE)
2386: || cmd.equals(StepEvent.STEP_ERROR)) {
2387: // Make sure the table updates its colors
2388: int index = scriptModel.getRowOf(step);
2389: if (index != -1) {
2390: scriptModel.fireTableRowsUpdated(index, index);
2391: }
2392: setStatusForStep(step);
2393: }
2394: }
2395:
2396: Resolver getResolverContext() {
2397: return scriptTable.getScriptContext();
2398: }
2399:
2400: /** From abbot.Resolver. */
2401: public ComponentReference getComponentReference(String refid) {
2402: return getResolverContext() != null ? getResolverContext()
2403: .getComponentReference(refid) : null;
2404: }
2405:
2406: /** From abbot.Resolver. */
2407: public ComponentReference getComponentReference(Component comp) {
2408: return getResolverContext() != null ? getResolverContext()
2409: .getComponentReference(comp) : null;
2410: }
2411:
2412: /** From abbot.Resolver. */
2413: public void addComponentReference(ComponentReference ref) {
2414: if (getResolverContext() == null) {
2415: throw new RuntimeException(Strings.get("NoContext"));
2416: }
2417: getResolverContext().addComponentReference(ref);
2418: }
2419:
2420: /** From abbot.Resolver. */
2421: public ComponentReference addComponent(Component comp) {
2422: if (getResolverContext() == null) {
2423: throw new RuntimeException(Strings.get("NoContext"));
2424: }
2425: return getResolverContext().addComponent(comp);
2426: }
2427:
2428: /** From abbot.Resolver. */
2429: public Collection getComponentReferences() {
2430: if (getResolverContext() == null) {
2431: return new HashSet();
2432: }
2433: return getResolverContext().getComponentReferences();
2434: }
2435:
2436: /** From abbot.Resolver. */
2437: public String getContext(Step step) {
2438: Resolver r = getResolverContext();
2439: if (r != null)
2440: return r.getContext(step);
2441: return "unknown";
2442: }
2443:
2444: /** From abbot.Resolver. */
2445: public File getDirectory() {
2446: if (getResolverContext() == null) {
2447: return new File(System.getProperty("user.dir"));
2448: }
2449: return getResolverContext().getDirectory();
2450: }
2451:
2452: /** From abbot.Resolver. */
2453: public void setProperty(String name, Object value) {
2454: if (getResolverContext() != null) {
2455: getResolverContext().setProperty(name, value);
2456: }
2457: }
2458:
2459: /** From abbot.Resolver. */
2460: public Object getProperty(String name) {
2461: if (getResolverContext() != null) {
2462: return getResolverContext().getProperty(name);
2463: }
2464: return null;
2465: }
2466:
2467: /** From abbot.Resolver. */
2468: public ClassLoader getContextClassLoader() {
2469: if (getResolverContext() != null) {
2470: return getResolverContext().getContextClassLoader();
2471: }
2472: return Thread.currentThread().getContextClassLoader();
2473: }
2474:
2475: public Hierarchy getHierarchy() {
2476: return hierarchy;
2477: }
2478:
2479: public String toString() {
2480: return name;
2481: }
2482:
2483: private void stepSelectionChanged() {
2484: // ensure the stop step colorization gets cleared
2485: stopStep = null;
2486: setStepEditor();
2487: setActionsEnabledState();
2488: }
2489:
2490: // FIXME this is slow on OSX
2491: private void setStepEditor(Step step) {
2492: final StepEditor editor;
2493: ignoreEvents = true;
2494: if (step != null
2495: && (editor = StepEditor.getEditor(step)) != null) {
2496: view.setEditor(editor);
2497: // make sure the script model listens to changes from the
2498: // step editor
2499: editor.addStepChangeListener(new StepChangeListener() {
2500: public void stepChanged(Step step) {
2501: int row = scriptModel.getRowOf(step);
2502: if (row != -1)
2503: scriptModel.fireTableRowsUpdated(row, row);
2504: }
2505: });
2506: } else {
2507: Log.debug("No editor available for '" + step + "'");
2508: view.setEditor(null);
2509: }
2510: ignoreEvents = false;
2511: // Update the component browser if the context changes
2512: view.getComponentBrowser().setResolver(getResolverContext());
2513: }
2514:
2515: private static String usage() {
2516: return ScriptEditor.class.getName() + " [suite classname]";
2517: }
2518:
2519: private class LaunchAction implements Runnable {
2520: private final Step which;
2521: private final Runnable onCompletion;
2522: private final String completionMessage;
2523: private final boolean launch;
2524:
2525: private LaunchAction(Step which, Runnable onCompletion,
2526: String completionMessage, boolean launch) {
2527: this .which = which;
2528: this .onCompletion = onCompletion;
2529: this .completionMessage = completionMessage;
2530: this .launch = launch;
2531: }
2532:
2533: public void run() {
2534: try {
2535: if (launch) {
2536: if (which instanceof Script) {
2537: UIContext context = which instanceof UIContext ? (UIContext) which
2538: : ((Script) which).getUIContext();
2539: if (context != null)
2540: context.launch(runner);
2541: }
2542: } else {
2543: runner.run(which);
2544: }
2545: if (completionMessage != null)
2546: setStatus(completionMessage);
2547: } catch (Throwable e) {
2548: // launch didn't work, get rid of it
2549: if (launch)
2550: terminate();
2551: setStatus(e.getMessage(), getStackTrace(e), ERROR);
2552: }
2553: SwingUtilities.invokeLater(new Runnable() {
2554: public void run() {
2555: if (waiter != null) {
2556: waiter.dispose();
2557: waiter = null;
2558: }
2559: isScriptRunning = false;
2560: setActionsEnabledState();
2561: if (onCompletion != null) {
2562: onCompletion.run();
2563: }
2564: }
2565: });
2566: }
2567: }
2568:
2569: /**
2570: * This class responds to changes in the script table's selection.
2571: */
2572: private class ScriptTableSelectionHandler implements
2573: ListSelectionListener {
2574: public void valueChanged(ListSelectionEvent ev) {
2575: if (ev.getValueIsAdjusting() || isScriptRunning)
2576: return;
2577: stepSelectionChanged();
2578: }
2579: }
2580:
2581: private void setStepEditor() {
2582: Step step = scriptTable.getSelectedRowCount() == 1 ? scriptModel
2583: .getStepAt(scriptTable.getSelectedRow())
2584: : null;
2585: setStepEditor(step);
2586: setStatusForStep(step);
2587: setActionsEnabledState();
2588: }
2589:
2590: /**
2591: * Handle the current test case/script selection from the current test
2592: * suite (if any).
2593: */
2594: private class ScriptSelectorItemHandler implements ItemListener {
2595: public void itemStateChanged(ItemEvent e) {
2596: if (ItemEvent.SELECTED == e.getStateChange()
2597: && !ignoreComboBox) {
2598: if (checkSaveBeforeClose()) {
2599: setScript(new Script((String) e.getItem(),
2600: hierarchy));
2601: } else {
2602: setScript(testScript);
2603: }
2604: }
2605: }
2606: }
2607:
2608: /** Provide a global editor context for anyone else that wants to display
2609: * an appropriate dialog message.
2610: */
2611: // FIXME move to ScriptEditorFrame
2612: private static ScriptEditor editor = null;
2613:
2614: /** This action shows an input dialog to collect arguments for a given
2615: * method on a ComponentTester class and adds an assertion or action to
2616: * the current script.
2617: */
2618: private class TesterMethodAction extends EditorAction implements
2619: Comparable {
2620: private Method method;
2621: private ComponentTester tester;
2622: private boolean wait;
2623:
2624: public TesterMethodAction(ComponentTester t, Method m, boolean w) {
2625: super (m.getName());
2626: putValue(NAME, getName(m));
2627: putValue(SMALL_ICON, getIcon(m));
2628: wait = w;
2629: tester = t;
2630: method = m;
2631: }
2632:
2633: public int compareTo(Object o) {
2634: if (o instanceof TesterMethodAction) {
2635: return getName().compareTo(
2636: ((TesterMethodAction) o).getName());
2637: }
2638: return 0;
2639: }
2640:
2641: public void actionPerformed(ActionEvent ev) {
2642: addTesterCall(method, tester, wait,
2643: getArgumentsDescription());
2644: }
2645:
2646: /** Return the human-readable menu name for the action. */
2647: private String getName(Method m) {
2648: String name = m.getName();
2649: // First try the resource bundle, then a system property
2650: String menu = Strings.get(name + ".menu", true);
2651: if (menu == null) {
2652: menu = System.getProperty(name + ".menu");
2653: // Default to the stripped-down method name
2654: if (menu == null) {
2655: if (name.startsWith("action")
2656: || name.startsWith("assert"))
2657: menu = name.substring(6);
2658: else if (name.startsWith("is"))
2659: menu = name.substring(2);
2660: else if (name.startsWith("get"))
2661: menu = name.substring(3);
2662: else
2663: menu = name;
2664: // Break up words if we can
2665: menu = TextFormat.wordBreak(menu);
2666: }
2667: }
2668: return menu;
2669: }
2670:
2671: private Icon getIcon(Method m) {
2672: // This loads the icon if it's in a jar file. Doesn't seem to work
2673: // when loading from a raw class file in the classpath.
2674: Icon icon = null;
2675: String path = Strings.get(m.getName() + ".icon", true);
2676: if (path == null) {
2677: path = System.getProperty(m.getName() + ".icon");
2678: }
2679: if (path != null) {
2680: URL url = ScriptEditor.class.getResource(path);
2681: if (url == null) {
2682: url = ScriptEditor.class.getResource("icons/"
2683: + path + ".gif");
2684: }
2685: if (url != null) {
2686: icon = new ImageIcon(url);
2687: }
2688: }
2689: return icon;
2690: }
2691:
2692: /** Provide a description of required arguments for this method. */
2693: private String getArgumentsDescription() {
2694: String name = method.getName();
2695: String cname = Robot.simpleClassName(method
2696: .getDeclaringClass());
2697: String args = Strings.get(cname + "." + name + ".args",
2698: true);
2699: if (args == null) {
2700: args = System.getProperty(cname + "." + name + ".args");
2701: if (args == null) {
2702: args = method.toString();
2703: }
2704: }
2705: return args;
2706: }
2707:
2708: public String getName() {
2709: return (String) getValue(NAME);
2710: }
2711:
2712: public int hashCode() {
2713: return getName().hashCode();
2714: }
2715:
2716: public boolean equals(Object o) {
2717: return o instanceof TesterMethodAction
2718: && getName().equals(
2719: ((TesterMethodAction) o).getName());
2720: }
2721: }
2722:
2723: /** Security manager to prevent applications under test from exiting.
2724: * StepRunner provides one of these, but we need to do additional
2725: * checking in the context of the script editor.
2726: */
2727: private class EditorSecurityManager extends NoExitSecurityManager {
2728:
2729: public void checkRead(String file) {
2730: // avoid annoying drive A: bug on w32
2731: }
2732:
2733: /** We do additional checking to allow exit from the editor itself. */
2734: public void checkExit(int status) {
2735: // Only allow exits by the root script editor
2736: if (!rootIsExiting) {
2737: super .checkExit(status);
2738: }
2739: }
2740:
2741: protected void exitCalled(int status) {
2742: terminate();
2743: }
2744: }
2745:
2746: private void hideView() {
2747: hiding = true;
2748: view.setVisible(false);
2749: }
2750:
2751: private void disposeView() {
2752: // Close this frame; flag to the view to allow it to be disposed
2753: exiting = true;
2754: view.dispose();
2755: }
2756:
2757: void dispose() {
2758: // Close any application under test
2759: terminate();
2760: normalizer.stopListening();
2761: hideView();
2762: disposeView();
2763:
2764: // Get rid of the security manager
2765: if (securityManager != null) {
2766: System.setSecurityManager(oldSecurityManager);
2767: securityManager = null;
2768: }
2769: }
2770:
2771: private static void bugCheck() {
2772: Window w = Costello.getSplashScreen();
2773: if (w != null) {
2774: // Test for bugs that the user should know about
2775: String[] bugs = Bugs.bugCheck(Costello.getSplashScreen());
2776: for (int i = 0; i < bugs.length; i++) {
2777: String title = Strings.get("BugWarning.title");
2778: String msg = TextFormat.dialog(bugs[i]);
2779: JOptionPane.showMessageDialog(w, msg, title,
2780: JOptionPane.WARNING_MESSAGE);
2781: }
2782: }
2783: }
2784:
2785: /** Launch the script editor, with an argument of either a test suite
2786: * class or a script filename.
2787: */
2788: public static void main(String[] args) {
2789: showEditor(new EditorContext(args));
2790: }
2791:
2792: /**
2793: * Launch the script editor, with an argument that represents a variety
2794: * of editor customizations.
2795: */
2796: public static void showEditor(EditorContext ec) {
2797: try {
2798: String args[] = Log.init(ec.getArguments());
2799:
2800: bugCheck();
2801:
2802: if (args.length > 1) {
2803: if (ec.isEmbedded()) {
2804: JOptionPane.showMessageDialog(null,
2805: "Invalid arguments when invoking abbot");
2806: return;
2807: } else {
2808: System.err.println("usage: " + usage());
2809: System.exit(1);
2810: }
2811:
2812: }
2813:
2814: editor = new ScriptEditor(ec);
2815:
2816: // Load the requested script or suite
2817: String arg = args.length == 1 ? args[0] : null;
2818: if (arg != null && new File(arg).exists()
2819: && Script.isScript(new File(arg))) {
2820: editor.setTestSuite(null);
2821: editor.setScript(arg);
2822: } else {
2823: editor.setTestSuite(arg);
2824: editor.setScript(0);
2825: }
2826:
2827: // Make sure everything currently extant is
2828: // ignored in the test hierarchy.
2829: // Skip this step if the context provides a test hierarchy
2830: editor.hierarchy.ignoreExisting();
2831:
2832: editor.view.pack();
2833: editor.view.setVisible(true);
2834: // Don't start listening to events until we're done generating them
2835: // (the "show" above can trigger deadlocks when the framework is
2836: // under test).
2837: editor.startListening();
2838: } catch (Throwable e) {
2839: if (editor != null)
2840: editor.dispose();
2841: System.err.println("Unexpected exception trying to launch "
2842: + "the script editor");
2843: e.printStackTrace();
2844: System.exit(1);
2845: }
2846: }
2847:
2848: /**
2849: * @return Returns whether the code under test is currently launched.
2850: */
2851: private boolean isAppLaunched() {
2852:
2853: // Override for embeded cases
2854: if (editorConfiguration.isEmbedded()) {
2855: return true;
2856: }
2857:
2858: if (testScript != null) {
2859: UIContext ctxt = testScript.getUIContext();
2860: if (ctxt != null)
2861: return ctxt.isLaunched();
2862: }
2863: return false;
2864: }
2865:
2866: private class RecordAllAction extends EditorAction {
2867: private Recorder recorder;
2868:
2869: public RecordAllAction(String actionName, Recorder rec,
2870: boolean extraModifier) {
2871: super (actionName);
2872: recorder = rec;
2873: int mask = InputEvent.SHIFT_MASK;
2874: if (extraModifier)
2875: mask |= InputEvent.ALT_MASK;
2876: putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(
2877: captureKey, mask));
2878: }
2879:
2880: public void actionPerformed(ActionEvent ev) {
2881: Log.debug("Menu action: start recording due to "
2882: + ev.getActionCommand());
2883: startRecording(recorder);
2884: }
2885: }
2886:
2887: private class EditorAboutAction extends EditorAction {
2888: public EditorAboutAction() {
2889: super (ACTION_EDITOR_ABOUT);
2890: }
2891:
2892: public void actionPerformed(ActionEvent e) {
2893: view.showAboutBox();
2894: }
2895: }
2896:
2897: private class EditorEmailAction extends EditorAction {
2898: public EditorEmailAction() {
2899: super (ACTION_EDITOR_EMAIL);
2900: }
2901:
2902: public void actionPerformed(ActionEvent e) {
2903: new Thread("mailing-list") {
2904: public void run() {
2905: try {
2906: Launcher.mail(Strings.get("editor.email.list"),
2907: Strings.get("editor.email.subject"),
2908: Strings.get("editor.email.body",
2909: new Object[] { BugReport
2910: .getSystemInfo() }));
2911: } catch (IOException e) {
2912: view.showWarning(e.getMessage());
2913: }
2914: }
2915: }.start();
2916: }
2917: }
2918:
2919: private class EditorBugReportAction extends EditorAction {
2920: public EditorBugReportAction() {
2921: super (ACTION_EDITOR_BUGREPORT);
2922: }
2923:
2924: public void actionPerformed(ActionEvent e) {
2925: new Thread("bug-report") {
2926: public void run() {
2927: try {
2928: Launcher.open(Strings.get("editor.submit_bug"));
2929: } catch (IOException e) {
2930: view.showWarning(e.getMessage());
2931: }
2932: }
2933: }.start();
2934: }
2935: }
2936:
2937: private class EditorWebsiteAction extends EditorAction {
2938: public EditorWebsiteAction() {
2939: super (ACTION_EDITOR_WEBSITE);
2940: }
2941:
2942: public void actionPerformed(ActionEvent e) {
2943: new Thread("mailing-list") {
2944: public void run() {
2945: try {
2946: Launcher.open(Strings.get("editor.website"));
2947: } catch (IOException e) {
2948: view.showWarning(e.getMessage());
2949: }
2950: }
2951: }.start();
2952: }
2953: }
2954:
2955: private class EditorUserGuideAction extends EditorAction {
2956: public EditorUserGuideAction() {
2957: super (ACTION_EDITOR_USERGUIDE);
2958: }
2959:
2960: public void actionPerformed(ActionEvent e) {
2961: new Thread("mailing-list") {
2962: public void run() {
2963: try {
2964: Launcher.open(Strings.get("editor.userguide"));
2965: } catch (IOException e) {
2966: view.showWarning(e.getMessage());
2967: }
2968: }
2969: }.start();
2970: }
2971: }
2972:
2973: private class EditorQuitAction extends EditorAction {
2974: public EditorQuitAction() {
2975: super (ACTION_EDITOR_QUIT);
2976: }
2977:
2978: public void actionPerformed(ActionEvent e) {
2979: quitApplication();
2980: }
2981: }
2982:
2983: private class ScriptNewAction extends EditorAction {
2984: public ScriptNewAction() {
2985: super (ACTION_SCRIPT_NEW);
2986: }
2987:
2988: public void actionPerformed(ActionEvent e) {
2989: newScript(false);
2990: }
2991: }
2992:
2993: private class ScriptDuplicateAction extends EditorAction {
2994: public ScriptDuplicateAction() {
2995: super (ACTION_SCRIPT_DUPLICATE);
2996: }
2997:
2998: public void actionPerformed(ActionEvent e) {
2999: newScript(true);
3000: }
3001: }
3002:
3003: private class ScriptOpenAction extends EditorAction {
3004: public ScriptOpenAction() {
3005: super (ACTION_SCRIPT_OPEN);
3006: }
3007:
3008: public void actionPerformed(ActionEvent e) {
3009: openScript();
3010: }
3011: }
3012:
3013: private class ScriptClearAction extends EditorAction {
3014: public ScriptClearAction() {
3015: super (ACTION_SCRIPT_CLEAR);
3016: }
3017:
3018: public void actionPerformed(ActionEvent e) {
3019: clearScript();
3020: }
3021: }
3022:
3023: private class ScriptDeleteAction extends EditorAction {
3024: public ScriptDeleteAction() {
3025: super (ACTION_SCRIPT_DELETE);
3026: }
3027:
3028: public void actionPerformed(ActionEvent e) {
3029: deleteScript();
3030: }
3031: }
3032:
3033: private class ScriptSaveAction extends EditorAction {
3034: public ScriptSaveAction() {
3035: super (ACTION_SCRIPT_SAVE);
3036: }
3037:
3038: public void actionPerformed(ActionEvent e) {
3039: saveScript();
3040: }
3041: }
3042:
3043: private class ScriptSaveAsAction extends EditorAction {
3044: public ScriptSaveAsAction() {
3045: super (ACTION_SCRIPT_SAVE_AS);
3046: }
3047:
3048: public void actionPerformed(ActionEvent e) {
3049: saveAsScript(false);
3050: }
3051: }
3052:
3053: private class ScriptRenameAction extends EditorAction {
3054: public ScriptRenameAction() {
3055: super (ACTION_SCRIPT_RENAME);
3056: }
3057:
3058: public void actionPerformed(ActionEvent e) {
3059: saveAsScript(true);
3060: }
3061: }
3062:
3063: private class ScriptCloseAction extends EditorAction {
3064: public ScriptCloseAction() {
3065: super (ACTION_SCRIPT_CLOSE);
3066: }
3067:
3068: public void actionPerformed(ActionEvent e) {
3069: closeScript();
3070: }
3071: }
3072:
3073: private class StepCutAction extends EditorAction {
3074: public StepCutAction() {
3075: super (ACTION_STEP_CUT);
3076: }
3077:
3078: public void actionPerformed(ActionEvent e) {
3079: cutSelection();
3080: }
3081: }
3082:
3083: private class StepMoveUpAction extends EditorAction {
3084: public StepMoveUpAction() {
3085: super (ACTION_STEP_MOVE_UP);
3086: }
3087:
3088: public void actionPerformed(ActionEvent e) {
3089: moveSelectionUp();
3090: }
3091: }
3092:
3093: private class StepMoveDownAction extends EditorAction {
3094: public StepMoveDownAction() {
3095: super (ACTION_STEP_MOVE_DOWN);
3096: }
3097:
3098: public void actionPerformed(ActionEvent e) {
3099: moveSelectionDown();
3100: }
3101: }
3102:
3103: private class StepGroupAction extends EditorAction {
3104: public StepGroupAction() {
3105: super (ACTION_STEP_GROUP);
3106: }
3107:
3108: public void actionPerformed(ActionEvent e) {
3109: groupSelection();
3110: }
3111: }
3112:
3113: private class RunAction extends EditorAction {
3114: public RunAction() {
3115: super (ACTION_RUN);
3116: }
3117:
3118: public void actionPerformed(ActionEvent e) {
3119: runScript(null);
3120: }
3121: }
3122:
3123: private class RunSelectedAction extends EditorAction {
3124: public RunSelectedAction() {
3125: super (ACTION_RUN_SELECTED);
3126: }
3127:
3128: public void actionPerformed(ActionEvent e) {
3129: runSelectedSteps();
3130: }
3131: }
3132:
3133: private class RunToAction extends EditorAction {
3134: public RunToAction() {
3135: super (ACTION_RUN_TO);
3136: }
3137:
3138: public void actionPerformed(ActionEvent e) {
3139: runScript(scriptTable.getSelectedStep());
3140: }
3141: }
3142:
3143: private class ExportHierarchyAction extends EditorAction {
3144: public ExportHierarchyAction() {
3145: super (ACTION_EXPORT_HIERARCHY);
3146: }
3147:
3148: public void actionPerformed(ActionEvent e) {
3149: exportHierarchy();
3150: }
3151: }
3152:
3153: private class SelectTestSuiteAction extends EditorAction {
3154: public SelectTestSuiteAction() {
3155: super (ACTION_SELECT_TESTSUITE);
3156: }
3157:
3158: public void actionPerformed(ActionEvent e) {
3159: browseTests();
3160: }
3161: }
3162:
3163: private class RunLaunchAction extends EditorAction {
3164: public RunLaunchAction() {
3165: super (ACTION_RUN_LAUNCH);
3166: }
3167:
3168: public void actionPerformed(ActionEvent e) {
3169: launch(true);
3170: }
3171: }
3172:
3173: private class RunTerminateAction extends EditorAction {
3174: public RunTerminateAction() {
3175: super (ACTION_RUN_TERMINATE);
3176: }
3177:
3178: public void actionPerformed(ActionEvent e) {
3179: terminate();
3180: }
3181: }
3182:
3183: private class InsertLaunchAction extends EditorAction {
3184: public InsertLaunchAction() {
3185: super (ACTION_INSERT_LAUNCH);
3186: }
3187:
3188: public void actionPerformed(ActionEvent e) {
3189: insertLaunch();
3190: }
3191: }
3192:
3193: private class InsertFixtureAction extends EditorAction {
3194: public InsertFixtureAction() {
3195: super (ACTION_INSERT_FIXTURE);
3196: }
3197:
3198: public void actionPerformed(ActionEvent e) {
3199: insertScript(true);
3200: }
3201: }
3202:
3203: private class InsertAppletAction extends EditorAction {
3204: public InsertAppletAction() {
3205: super (ACTION_INSERT_APPLET);
3206: }
3207:
3208: public void actionPerformed(ActionEvent e) {
3209: insertApplet();
3210: }
3211: }
3212:
3213: private class InsertTerminateAction extends EditorAction {
3214: public InsertTerminateAction() {
3215: super (ACTION_INSERT_TERMINATE);
3216: }
3217:
3218: public void actionPerformed(ActionEvent e) {
3219: insertTerminate();
3220: }
3221: }
3222:
3223: private class InsertCallAction extends EditorAction {
3224: public InsertCallAction() {
3225: super (ACTION_INSERT_CALL);
3226: }
3227:
3228: public void actionPerformed(ActionEvent e) {
3229: insertCall(false);
3230: }
3231: }
3232:
3233: private class InsertSampleAction extends EditorAction {
3234: public InsertSampleAction() {
3235: super (ACTION_INSERT_SAMPLE);
3236: }
3237:
3238: public void actionPerformed(ActionEvent e) {
3239: insertCall(true);
3240: }
3241: }
3242:
3243: private class InsertSequenceAction extends EditorAction {
3244: public InsertSequenceAction() {
3245: super (ACTION_INSERT_SEQUENCE);
3246: }
3247:
3248: public void actionPerformed(ActionEvent e) {
3249: insertSequence();
3250: }
3251: }
3252:
3253: private class InsertScriptAction extends EditorAction {
3254: public InsertScriptAction() {
3255: super (ACTION_INSERT_SCRIPT);
3256: }
3257:
3258: public void actionPerformed(ActionEvent e) {
3259: insertScript(false);
3260: }
3261: }
3262:
3263: private class InsertCommentAction extends EditorAction {
3264: public InsertCommentAction() {
3265: super (ACTION_INSERT_COMMENT);
3266: }
3267:
3268: public void actionPerformed(ActionEvent e) {
3269: insertComment();
3270: }
3271: }
3272:
3273: private class InsertExpressionAction extends EditorAction {
3274: public InsertExpressionAction() {
3275: super (ACTION_INSERT_EXPRESSION);
3276: }
3277:
3278: public void actionPerformed(ActionEvent e) {
3279: insertExpression();
3280: }
3281: }
3282:
3283: private class InsertAnnotationAction extends EditorAction {
3284: public InsertAnnotationAction() {
3285: super (ACTION_INSERT_ANNOTATION);
3286: }
3287:
3288: public void actionPerformed(ActionEvent e) {
3289: insertAnnotation();
3290: }
3291: }
3292:
3293: private class GetVMArgsAction extends EditorAction {
3294: public GetVMArgsAction() {
3295: super (ACTION_GET_VMARGS);
3296: }
3297:
3298: public void actionPerformed(ActionEvent e) {
3299: getVMArgs();
3300: }
3301: }
3302:
3303: private class SelectComponentAction extends EditorAction {
3304: public SelectComponentAction() {
3305: super (ACTION_SELECT_COMPONENT);
3306: putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(selectKey,
3307: InputEvent.SHIFT_MASK));
3308: }
3309:
3310: public void actionPerformed(ActionEvent e) {
3311: // This is really only for documentation purposes; there is
3312: // nothing to capture if the menu item can be activated, since
3313: // the editor has focus when the menu is selected.
3314: view.showWarning(Strings
3315: .get("actions.select-component.desc"));
3316: }
3317: }
3318:
3319: private class CaptureComponentAction extends EditorAction {
3320: public CaptureComponentAction() {
3321: super (ACTION_CAPTURE_COMPONENT);
3322: putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(selectKey,
3323: InputEvent.SHIFT_MASK | InputEvent.ALT_MASK));
3324: }
3325:
3326: public void actionPerformed(ActionEvent e) {
3327: // This is really only for documentation purposes; there is
3328: // nothing to capture if the menu item can be activated, since
3329: // the editor has focus when the menu is selected.
3330: view.showWarning(Strings
3331: .get("actions.capture-component.desc"));
3332: }
3333: }
3334:
3335: private class CaptureImageAction extends EditorAction {
3336: public CaptureImageAction() {
3337: super (ACTION_CAPTURE_IMAGE);
3338: putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(
3339: captureImageKey, InputEvent.SHIFT_MASK));
3340: }
3341:
3342: public void actionPerformed(ActionEvent e) {
3343: // This is really only for documentation purposes; there is
3344: // nothing to capture if the menu item can be activated, since
3345: // the editor has focus when the menu is selected.
3346: view.showWarning(Strings.get("actions.capture-image.desc"));
3347: }
3348: }
3349:
3350: private class ToggleForkedAction extends EditorToggleAction {
3351: public ToggleForkedAction() {
3352: super (ACTION_TOGGLE_FORKED);
3353: }
3354:
3355: public void actionPerformed(ActionEvent e) {
3356: forkedToggle();
3357: }
3358: }
3359:
3360: private class ToggleSlowPlaybackAction extends EditorToggleAction {
3361: public ToggleSlowPlaybackAction() {
3362: super (ACTION_TOGGLE_SLOW_PLAYBACK);
3363: }
3364:
3365: public void actionPerformed(ActionEvent e) {
3366: slowPlaybackToggle();
3367: }
3368: }
3369:
3370: private class ToggleAWTModeAction extends EditorToggleAction {
3371: public ToggleAWTModeAction() {
3372: super (ACTION_TOGGLE_AWT_MODE);
3373: }
3374:
3375: public void actionPerformed(ActionEvent e) {
3376: awtModeToggle();
3377: }
3378: }
3379:
3380: private class ToggleStopOnErrorAction extends EditorToggleAction {
3381: public ToggleStopOnErrorAction() {
3382: super (ACTION_TOGGLE_STOP_ON_ERROR);
3383: }
3384:
3385: public void actionPerformed(ActionEvent e) {
3386: stopOnErrorToggle();
3387: }
3388: }
3389:
3390: private class ToggleStopOnFailureAction extends EditorToggleAction {
3391: public ToggleStopOnFailureAction() {
3392: super (ACTION_TOGGLE_STOP_ON_FAILURE);
3393: }
3394:
3395: public void actionPerformed(ActionEvent e) {
3396: stopOnFailureToggle();
3397: }
3398: }
3399:
3400: private class EditorStepRunner extends StepRunner {
3401: /** We use a single runner througout the editor's lifetime,
3402: * so one saved UI context will suffice.
3403: */
3404: public EditorStepRunner() {
3405: super (new AWTFixtureHelper());
3406: }
3407:
3408: public Hierarchy getHierarchy() {
3409: return ScriptEditor.this .hierarchy;
3410: }
3411:
3412: public void terminate() {
3413: super.terminate();
3414: setActionsEnabledState();
3415: view.getComponentBrowser().refresh();
3416: }
3417: }
3418: }
|