1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.exec;
  28 
  29 import com.sun.interview.Help;
  30 import java.awt.CardLayout;
  31 import java.awt.Component;
  32 import java.awt.Container;
  33 import java.awt.Dimension;
  34 import java.awt.AWTEvent;
  35 import java.awt.FocusTraversalPolicy;
  36 import java.awt.KeyboardFocusManager;
  37 import java.awt.event.ActionEvent;
  38 import java.awt.event.ActionListener;
  39 import java.io.File;
  40 import java.io.FileNotFoundException;
  41 import java.io.IOException;
  42 import java.util.HashMap;
  43 import java.util.Iterator;
  44 import java.util.Map;
  45 import java.util.Set;
  46 import java.util.TreeSet;
  47 import javax.swing.BorderFactory;
  48 import javax.swing.ButtonGroup;
  49 import javax.swing.JCheckBoxMenuItem;
  50 import javax.swing.JComponent;
  51 import javax.swing.JFileChooser;
  52 import javax.swing.JMenu;
  53 import javax.swing.JMenuBar;
  54 import javax.swing.JMenuItem;
  55 import javax.swing.JOptionPane;
  56 import javax.swing.JPanel;
  57 import javax.swing.JRadioButtonMenuItem;
  58 import javax.swing.JSplitPane;
  59 import javax.swing.KeyStroke;
  60 import javax.swing.WindowConstants;
  61 import javax.swing.event.ChangeEvent;
  62 import javax.swing.event.ChangeListener;
  63 import javax.swing.event.MenuEvent;
  64 import javax.swing.event.MenuListener;
  65 
  66 import com.sun.interview.Interview;
  67 import com.sun.interview.Question;
  68 import com.sun.interview.wizard.QuestionRenderer;
  69 import com.sun.javatest.InterviewParameters;
  70 import com.sun.javatest.TestSuite;
  71 import com.sun.javatest.WorkDirectory;
  72 import com.sun.javatest.exec.WorkDirChooseTool.ExecModelStub;
  73 import com.sun.javatest.tool.FileChooser;
  74 import com.sun.javatest.tool.FileHistory;
  75 import com.sun.javatest.tool.HelpLink;
  76 import com.sun.javatest.tool.Preferences;
  77 import com.sun.javatest.tool.ToolDialog;
  78 import com.sun.javatest.tool.UIFactory;
  79 import com.sun.javatest.tool.jthelp.HelpID;
  80 import com.sun.javatest.tool.jthelp.JHelpContentViewer;
  81 import com.sun.javatest.util.Debug;
  82 import java.util.ArrayList;
  83 import java.util.List;
  84 
  85 /**
  86  * Dialog to edit InterviewParameters object.
  87  *
  88  * InterviewEditor keeps reference to the main InterviewParameters object,
  89  * but never change it.
  90  * <br>
  91  * Before editing interview the main InterviewParameters object is synced
  92  * with the view object.
  93  * <br>
  94  * When view object is loaded or saved, all registered observers are notified.
  95  *
  96  */
  97 public class InterviewEditor extends ToolDialog {
  98 
  99     public InterviewEditor(JComponent parent, UIFactory uif,
 100             InterviewParameters ip) {
 101 
 102         super(parent, uif, "ce", ToolDialog.MODAL_DOCUMENT);
 103         WorkDirectory wd = ip.getWorkDirectory();
 104         if (wd == null) {
 105             throw new IllegalArgumentException(uif.getI18NString("ce.wdNull.err"));
 106         }
 107         mainConfig = ip;
 108         try {
 109             //this.key = key;
 110             this.ext = getExtention();
 111             viewConfig = ip.getTestSuite().createInterview();
 112             viewConfig.setWorkDirectory(ip.getWorkDirectory());
 113             history = FileHistory.getFileHistory(wd, getHistoryFileName());
 114         } catch (TestSuite.Fault e) {
 115             // ignore, for now; should not happen
 116         }
 117     }
 118     public InterviewEditor(JComponent parent, UIFactory uif,
 119             InterviewParameters ip, ContextManager cm) {
 120         this(parent, uif, ip);
 121         setContextManager(cm);
 122     }
 123 
 124     /**
 125      * Sets contextManager to the passed value.
 126      * @param cm - ContextManager to use
 127      */
 128     void setContextManager(ContextManager cm) {
 129         this.contextManager = cm;
 130     }
 131 
 132     /**
 133      * Returns previously created or set ContextManager. If not set, creates
 134      * the new instance.
 135      */
 136     ContextManager getContextManager() {
 137         if (contextManager == null) {
 138             try {
 139                 contextManager = (ContextManager) ((Class.forName(
 140                         "com.sun.javatest.exec.ContextManager")).newInstance());
 141             } catch (Exception e) {
 142                 e.printStackTrace();
 143             }
 144         }
 145         return contextManager;
 146     }
 147 
 148 
 149     /**
 150      * Returns extension for files to be saved. Subclasses like TemplateEditor
 151      * might override this method.
 152      *
 153      * @return default extension
 154      */
 155     protected String getExtention() {
 156         return CONFIG_EXTENSION;
 157     }
 158 
 159     /**
 160      * Returns file name to store history of configuration files.
 161      * This implementation returns "configHistory.jtl". Subclasses might
 162      * override this method to return alternative value.
 163      */
 164     protected String getHistoryFileName() {
 165         return CONFIG_HISTORY;
 166     }
 167 
 168     private void setRestorer(CE_View view) {
 169         getRestorer().setRestorePolicy(Restorer.RESTORE_ALL);
 170         getRestorer().setWindowKey(getRestorerWindowKey(view == fullView));
 171     }
 172 
 173     protected String getRestorerWindowKey(boolean isFullView) {
 174         return "confEdit.config" + (isFullView ? ".f" : ".s");
 175     }
 176 
 177     /**
 178      * Adds passed file to the history.
 179      * @param f - file to be added.
 180      */
 181     void addToHistory(File f) {
 182         if (history == null) {
 183             WorkDirectory wd = viewConfig.getWorkDirectory();
 184             if (wd == null) {
 185                 return;
 186             }
 187             history = FileHistory.getFileHistory(wd, getHistoryFileName());
 188         }
 189         history.add(f);
 190     }
 191 
 192     /**
 193      * Syncs mainConfig and viewConfig
 194      */
 195     private void sync() {
 196         try {
 197             copy(mainConfig, viewConfig);
 198         } catch (Interview.Fault e) {
 199             uif.showError("ce.show.error", e.getMessage());
 200         }
 201     }
 202 
 203     /**
 204      * Starts editing new config. Supposed to be called outside.
 205      */
 206     public void newConfig() {
 207         try {
 208             InterviewParameters newConfig = viewConfig.getTestSuite().createInterview();
 209             copy(newConfig, viewConfig);
 210         } catch (TestSuite.Fault e) {
 211             uif.showError("ce.show.error", e.getMessage());
 212         } catch (Interview.Fault e) {
 213             uif.showError("ce.show.error", e.getMessage());
 214         }
 215         show(FULL_MODE);
 216     }
 217 
 218     /**
 219      * Start editing empty configuration.
 220      */
 221     private void newConfigAsk() {
 222         if (askAndSave("ce.clear.warn")) {
 223             newConfig();
 224         }
 225     }
 226     /**
 227      * Show dialog.
 228      */
 229     public void edit(int mode) {
 230         sync();
 231         show(mode);
 232     }
 233 
 234     /**
 235      * @return  mode that will be used by WorkDirChooseTool to select file.
 236      */
 237     public int getFileChooserMode() {
 238         return WorkDirChooseTool.LOAD_CONFIG;
 239     }
 240 
 241     /**
 242      * Show choose file dialog and then load new file.
 243      * Supposed to be invoked from outside of editor.
 244      * Doesn't expect that viewConfig can be changed.
 245      */
 246     public void loadConfig() {
 247         loadConfig0(false);
 248     }
 249 
 250     /**
 251      * Show choose file dialog and then load new file.
 252      * The dialog depends on fileChooserMode setting. It can be either
 253      * simple JFileChooser or "advanced" home made file chooser.
 254      * @param ask if true, dialog asking whether to save changes will appear
 255      *        in case of unsaved changes.
 256      */
 257     protected void loadConfig0(boolean ask) {
 258         TestSuite ts = viewConfig.getTestSuite();
 259         ExecModelStub em = new ExecModelStub(ts, contextManager);
 260         try {
 261             em.setWorkDir(mainConfig.getWorkDirectory(), true);
 262         } catch (Exception ignore) {
 263             // stub never throws exceptions
 264         }
 265         WorkDirChooseTool fc =  WorkDirChooseTool.getTool((JComponent)parent,
 266                 uif, em, getFileChooserMode(), ts, true);
 267         WorkDirChooseTool.ChosenFileHandler cfh =
 268                 new WorkDirChooseTool.ChosenFileHandler();
 269         fc.setChosenFileHandler(cfh);
 270         fc.doTool();
 271 
 272         File f = cfh.file;
 273         if (f != null && (!ask || askAndSave("ce.load.warn"))) {
 274             loadConfigFromFile(f);
 275             if (cfh.isEditConfig && !isVisible()) {
 276                 edit(FULL_MODE);
 277             }
 278         }
 279     }
 280 
 281     /**
 282      * Works similar to loadConfig(), but asks to save changes if any
 283      * before reload.
 284      */
 285     private void loadConfigAsk() {
 286         loadConfig0(true);
 287     }
 288 
 289     /**
 290      *
 291      * @param f
 292      */
 293     public void loadAndEdit(File f) {
 294         if (f == null) {
 295             return;
 296         }
 297         loadConfigFromFile(f);
 298         notifyObservers();
 299         show(FULL_MODE);
 300     }
 301 
 302     /**
 303      * Shows file chooser dialog.
 304      * @return chosen file or null.
 305      */
 306     private File chooseConfigFile() {
 307         File mainConfigFile = mainConfig.getFile();
 308         FileChooser fileChooser = getFileChooser();
 309         if (mainConfigFile != null)
 310             fileChooser.setCurrentDirectory(mainConfigFile.getParentFile());
 311         return  loadConfigFile(getContextManager(), parent, uif, fileChooser);
 312     }
 313 
 314     /**
 315      * Updates viewConfig, notifies observers of the change.
 316      * @param file File to load.
 317      */
 318     public void loadConfigFromFile(File file) {
 319         if (file == null) {
 320             return;
 321         }
 322         try {
 323             viewConfig.load(file);
 324             if (currView != null && currView.isShowing())
 325                 currView.load();
 326             addToHistory(file);
 327             updateTitle();
 328             notifyObservers();
 329         } catch (FileNotFoundException e) {
 330             uif.showError("ce.load.cantFindFile", file);
 331         } catch (IOException e) {
 332             uif.showError("ce.load.error", new Object[] { file, e } );
 333         } catch (Interview.Fault e) {
 334             uif.showError("ce.load.error", new Object[] { file, e.getMessage() } );
 335         }
 336     }
 337 
 338 
 339     public void save() {
 340         save0();
 341     }
 342 
 343     // return true if saved, false if cancelled/error
 344     private boolean save0() {
 345         // Use the filename saved in the viewConfig.
 346         // By default, this will have been copied from the mainConfig,
 347         // but it may have been cleared if clear() has been called, thereby
 348         // making "save" behave as "saveAs".
 349         return save0(viewConfig.getFile());
 350     }
 351 
 352     public void saveAs() {
 353         save0(null);
 354     }
 355 
 356     // return true if saved, false if cancelled/error
 357     private boolean save0(File file) {
 358         if (file == null) {
 359             File mainConfigFile = mainConfig.getFile();
 360             File mainConfigDir = (mainConfigFile == null ? null : mainConfigFile.getParentFile());
 361             file = getSaveFile(mainConfigDir);
 362             if (file == null)
 363                 return false; // exit without saving
 364         }
 365 
 366         try {
 367             if (currView != null) {
 368                 currView.save();
 369             }
 370             viewConfig.setFile(file);   // for subsequent use
 371             doSave(file);
 372             addToHistory(file);
 373 
 374             updateTitle();
 375             notifyObservers();
 376 
 377             return true;
 378         }
 379         catch (IOException e) {
 380             if (!file.canWrite())
 381                 uif.showError("ce.save.cantWriteFile", file);
 382             else if (e instanceof FileNotFoundException)
 383                 uif.showError("ce.save.cantFindFile", file);
 384             else
 385                 uif.showError("ce.save.error", new Object[] { file, e } );
 386         }
 387         catch (Interview.Fault e) {
 388             uif.showError("ce.save.error", new Object[] { file, e.getMessage() } );
 389         }
 390 
 391         return false;
 392     }
 393 
 394     /**
 395      * Does actual save work. should be overriden, when needed.
 396      */
 397     protected void doSave(File file) throws Interview.Fault, IOException {
 398         viewConfig.save(file);
 399     }
 400 
 401     private File getSaveFile(File dir) {
 402         FileChooser fileChooser = getFileChooser();
 403         fileChooser.setDialogTitle(uif.getI18NString("ce.save.title"));
 404         return saveConfigFile(getContextManager(), parent, uif, fileChooser, dir,
 405                 this.templateMode);
 406     }
 407 
 408     private FileChooser getFileChooser() {
 409         FileChooser fileChooser = new FileChooser(true);
 410         fileChooser.addChoosableExtension(ext, uif.getI18NString("ce.jtiFiles"));
 411         return fileChooser;
 412     }
 413 
 414     /**
 415      * In viewConfig differs from mainConfig asks user whether save changes
 416      * or not. Save changes in case of positive answer.
 417      * @param que
 418      * @return false iff user said "cancel".
 419      */
 420     private boolean askAndSave(String question) {
 421         if (isEdited()) {
 422             int rc = uif.showYesNoCancelDialog(question);
 423 
 424             switch (rc) {
 425                 case JOptionPane.YES_OPTION: {
 426                     save();
 427                     return true;}
 428                 case JOptionPane.NO_OPTION: return true;
 429                 default: return false;
 430             }
 431         } else {
 432             return true;
 433         }
 434     }
 435 
 436     public void revert() {
 437         if (!isEdited())
 438             return;
 439 
 440         int rc = uif.showOKCancelDialog("ce.revert.warn");
 441         if (rc != JOptionPane.OK_OPTION)
 442             return;
 443 
 444         try {
 445             copy(mainConfig, viewConfig);
 446             if (currView != null && currView.isShowing())
 447                 currView.load();
 448             updateTitle();
 449         } catch (Interview.Fault e) {
 450             uif.showError("ce.revert", e.getMessage());
 451         }
 452     }
 453 
 454     public void setRunPending(boolean b) {
 455         runPending = b;
 456     }
 457 
 458     public boolean isRunPending() {
 459         return runPending;
 460     }
 461 
 462     public void show() {
 463         if (stdView == null)
 464             initGUI();
 465 
 466         show(DEFAULT_MODE);
 467     }
 468 
 469     public void updateMenu() {
 470         FileHistory h = FileHistory.getFileHistory(viewConfig.getWorkDirectory(), getHistoryFileName());
 471         recentConfigMenu.setEnabled(h.getLatestEntry() != null);
 472     }
 473 
 474     public void show(int mode) {
 475         if (stdView == null) {
 476             initGUI();
 477         }
 478         updateTitle();
 479         updateMenu();
 480         switch (mode) {
 481             case DEFAULT_MODE:
 482                 show(getDefaultView());
 483                 break;
 484 
 485             case FULL_MODE:
 486                 show(fullView);
 487                 break;
 488 
 489             case STD_MODE:
 490                 show(stdView);
 491                 break;
 492 
 493             case STD_TESTS_MODE:
 494                 stdView.showTab(CE_StdView.TESTS_PANE);
 495                 show(stdView);
 496                 break;
 497 
 498             case STD_EXCLUDE_LIST_MODE:
 499                 stdView.showTab(CE_StdView.EXCLUDE_LIST_PANE);
 500                 show(stdView);
 501                 break;
 502 
 503             case STD_KEYWORDS_MODE:
 504                 stdView.showTab(CE_StdView.KEYWORDS_PANE);
 505                 show(stdView);
 506                 break;
 507 
 508             case STD_KFL_MODE:
 509                 stdView.showTab(CE_StdView.KFL_PANE);
 510                 show(stdView);
 511                 break;
 512 
 513             case STD_PRIOR_STATUS_MODE:
 514                 stdView.showTab(CE_StdView.PRIOR_STATUS_PANE);
 515                 show(stdView);
 516                 break;
 517 
 518             case STD_ENVIRONMENT_MODE:
 519                 stdView.showTab(CE_StdView.ENVIRONMENT_PANE);
 520                 show(stdView);
 521                 break;
 522 
 523             case STD_CONCURRENCY_MODE:
 524                 stdView.showTab(CE_StdView.CONCURRENCY_PANE);
 525                 show(stdView);
 526                 break;
 527 
 528             case STD_TIMEOUT_FACTOR_MODE:
 529                 stdView.showTab(CE_StdView.TIMEOUT_FACTOR_PANE);
 530                 show(stdView);
 531                 break;
 532 
 533             default:
 534                 throw new IllegalArgumentException();
 535         }
 536     }
 537 
 538     public void show(ActionListener closeListener) {
 539         this.closeListener = closeListener;
 540         show();
 541     }
 542 
 543     public void show(int mode, ActionListener closeListener, boolean isTemplateMode) {
 544         this.closeListener = closeListener;
 545         show(mode);
 546     }
 547 
 548     public static final int DEFAULT_MODE = 0;
 549     public static final int FULL_MODE = 1;
 550     public static final int STD_MODE = 2;
 551     public static final int STD_TESTS_MODE = 3;
 552     public static final int STD_EXCLUDE_LIST_MODE = 4;
 553     public static final int STD_KEYWORDS_MODE = 5;
 554     public static final int STD_PRIOR_STATUS_MODE = 6;
 555     public static final int STD_ENVIRONMENT_MODE = 7;
 556     public static final int STD_CONCURRENCY_MODE = 8;
 557     public static final int STD_TIMEOUT_FACTOR_MODE = 9;
 558     public static final int TEMPLATE_FULL_MODE = 10;
 559     public static final int STD_KFL_MODE = 11;
 560 
 561     private void show(CE_View newView) {
 562 
 563         // update viewConfig from currView (if showing) else mainConfig
 564         if (currView != null && currView.isShowing()) {
 565             currView.save();
 566         }
 567         setRestorer(newView);
 568         setView(newView);
 569         setVisible(true);
 570     }
 571 
 572     @Override
 573     public void setVisible(boolean isVisible) {
 574         super.setVisible(isVisible);
 575         notifyObserversOfVisibility(isVisible);
 576     }
 577 
 578     private void setView(CE_View newView) {
 579         if (newView == null)
 580             throw new NullPointerException();
 581 
 582         if (currView != null && currView == newView) {
 583             currView.load();
 584         }
 585 
 586         if (currView != newView) {
 587             // note whether the focus is in the current view
 588             KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
 589             Component fo = kfm.getPermanentFocusOwner();
 590             boolean focusInView = (fo != null && currView != null && currView.isAncestorOf(fo));
 591 
 592             currView = newView;
 593 
 594             // update currView from viewConfig
 595             currView.load();
 596 
 597             // set up the appropriate view and controls
 598             ((CardLayout)(views.getLayout())).show(views, currView.getName());
 599 
 600             // The following is a workaround for what may be a JDK bug.
 601             // As a result of changing the view, the permanent focus owner may no longer
 602             // be showing, and therefore not accepting any keyboard input.
 603             if (focusInView) {
 604                 Container fcr = (currView.isFocusCycleRoot() ? currView : currView.getFocusCycleRootAncestor());
 605                 FocusTraversalPolicy ftp = fcr.getFocusTraversalPolicy();
 606                 Component c = ftp.getDefaultComponent(fcr);
 607                 if (c != null)
 608                     c.requestFocusInWindow();
 609             }
 610 
 611             boolean currIsFull = (currView == fullView);
 612             markerMenu.setEnabled(currIsFull);
 613             searchMenu.setEnabled(currIsFull);
 614             (currIsFull ? viewFullBtn : viewStdBtn).setSelected(true);
 615             viewTagCheckBox.setEnabled(currIsFull);
 616 
 617             if (detailsBrowser != null)
 618                 detailsBrowser.setQuestionInfoEnabled(currIsFull);
 619 
 620             updateTitle();
 621         }
 622     }
 623 
 624     public void close() {
 625         if (canInterruptTemplateCreation()) {
 626             doClose();
 627         } else {
 628             uif.showError("ce.force_close");
 629         }
 630     }
 631 
 632     public void doClose() {
 633         if (currView != null && !currView.isOKToClose()) {
 634             if (afterCloseCommand != null) {
 635                 afterCloseCommand.run();
 636                 afterCloseCommand = null;
 637             }
 638             return;
 639         }
 640 
 641         close(true);
 642     }
 643 
 644     protected void windowClosingAction(AWTEvent e) {
 645         if (!canInterruptTemplateCreation()) {
 646             uif.showError("ce.force_close");
 647             return;
 648         }
 649 
 650         if(fullView.isVisible()) {
 651             fullView.prepareClosing();
 652         }
 653         doClose();
 654     }
 655 
 656 
 657     private void close(boolean checkIfEdited) {
 658         if (currView == null)
 659             return;
 660 
 661         if (!isShowing())
 662             return;
 663 
 664         if (checkIfEdited && isEdited()) {
 665             int rc = uif.showYesNoCancelDialog("ce.close.warn");
 666             switch (rc) {
 667             case JOptionPane.YES_OPTION:
 668                 if (save0()) {
 669                     break;
 670                 } else {
 671                     if (afterCloseCommand != null) {
 672                         afterCloseCommand.run();
 673                         afterCloseCommand = null;
 674                     }
 675                     return;
 676                 }
 677 
 678             case JOptionPane.NO_OPTION:
 679                 break;
 680             default:
 681                 if (afterCloseCommand != null) {
 682                     afterCloseCommand.run();
 683                     afterCloseCommand = null;
 684                 }
 685                 return;
 686             }
 687         }
 688 
 689         setVisible(false);
 690 
 691         // closeListener may have been set by show(ActionListener)
 692         if (closeListener != null) {
 693             ActionEvent e = new ActionEvent(this,
 694                                             ActionEvent.ACTION_PERFORMED,
 695                                             CLOSE);
 696             closeListener.actionPerformed(e);
 697             closeListener = null;
 698         }
 699 
 700         if (afterCloseCommand != null) {
 701             afterCloseCommand.run();
 702             afterCloseCommand = null;
 703         }
 704 
 705     }
 706 
 707     public void setCheckExcludeListListener(ActionListener l) {
 708         if (stdView == null)
 709             initGUI();
 710 
 711         stdView.setCheckExcludeListListener(l);
 712     }
 713 
 714     boolean isCurrentQuestionChanged() {
 715         if (currView != null && currView.isShowing())
 716             currView.save();
 717 
 718         Question mq = mainConfig.getCurrentQuestion();
 719         Question vq = viewConfig.getCurrentQuestion();
 720         return !equal(mq.getTag(), vq.getTag());
 721     }
 722 
 723 
 724     boolean isEdited() {
 725         if (currView != null && currView.isShowing())
 726             currView.save();
 727 
 728         return !equal(mainConfig, viewConfig);
 729     }
 730 
 731     /**
 732      * Compares two InterviewParameters objects for equivalence.
 733      * Two interview are equivalent when they both provide the same set
 734      * of questions and all corresponding questions have the same values.
 735      *
 736      * @param a first interview
 737      * @param b second interview
 738      * @return true, iff two interviews are equivalent.
 739      */
 740     public static boolean equal(InterviewParameters a, InterviewParameters b) {
 741         if (a == b) {
 742             return true;
 743         }
 744 
 745         if (a.isTemplate() != b.isTemplate()) {
 746             return false;
 747         }
 748         // do ez checks first
 749         if (a.getMarkersEnabled() != b.getMarkersEnabled()
 750             || a.getMarkersFilterEnabled() != b.getMarkersFilterEnabled()) {
 751             return false;
 752         }
 753 
 754         Map<String, Question> aQuestions = a.getAllQuestions();
 755         Map<String, Question> bQuestions = b.getAllQuestions();
 756 
 757         Set<String> keys = new TreeSet<>();
 758         keys.addAll(aQuestions.keySet());
 759         keys.addAll(bQuestions.keySet());
 760 
 761         for (Iterator iter = keys.iterator(); iter.hasNext(); ) {
 762             String key = (String) (iter.next());
 763             Question aq = aQuestions.get(key);
 764             Question bq = bQuestions.get(key);
 765             if (aq == null || bq == null) {
 766                 return false;
 767             }
 768             if (!aq.equals(bq)) {
 769                 String empty = "";
 770                 boolean eq = (aq.getStringValue() == null &&
 771                         empty.equals(bq.getStringValue())) ||
 772                         (empty.equals(aq.getStringValue()) &&
 773                         bq.getStringValue() == null);
 774                 if(!eq) {
 775                     /*
 776                      Hopefully, question reloading is not required anymore,
 777                      not because questions are equal now, but because
 778                      the unexpected dialog no longer appears...
 779 
 780                       // if aq is not set, it will be set to the default value
 781                       // (if the default value had been specified for aq before)
 782                      aq.reload();
 783                     if (!aq.equals(bq)) {
 784                         return false;
 785                     }
 786                      */
 787                     return false;
 788                 }
 789             }
 790         }
 791         // Checking external values
 792         Set aKeys = a.getPropertyKeys();
 793         Set bKeys = b.getPropertyKeys();
 794 
 795         if (aKeys == null || bKeys == null) {
 796             return aKeys == bKeys;
 797         }
 798 
 799         if (aKeys.size() != bKeys.size()) {
 800             return false;
 801         }
 802 
 803         for (Iterator iter = aKeys.iterator(); iter.hasNext(); ) {
 804             String key = (String)iter.next();
 805             if (!bKeys.contains(key)) {
 806                 return false;
 807             }
 808 
 809             String aProp = a.retrieveProperty(key);
 810             String bProp = b.retrieveProperty(key);
 811             if (!equal(aProp, bProp)) {
 812                 return false;
 813             }
 814         }
 815 
 816 
 817         return true;
 818     }
 819 
 820     /**
 821      * Registers new observer
 822      * @param o - observer to be added to the list
 823      */
 824     public void addObserver(Observer o) {
 825         if (o != null && !observers.contains(o)) {
 826             observers.add(o);
 827         }
 828     }
 829     /**
 830      * Removes observer from the list
 831      * @param o - observer to be removed from the list
 832      */
 833     public void removeObserver(Observer o) {
 834         if (o != null) {
 835             observers.remove(o);
 836         }
 837     }
 838     /**
 839      * Notifies registered observers of the change happened to viewConfig
 840      */
 841     protected void notifyObservers() {
 842         for (Observer obs: observers) {
 843             obs.changed(viewConfig);
 844         }
 845     }
 846     /**
 847      * Notifies registered observers of setVisible() method has been called.
 848      */
 849     protected void notifyObserversOfVisibility(boolean isVisible) {
 850         for (Observer obs: observers) {
 851             obs.changedVisibility(isVisible, this);
 852         }
 853     }
 854 
 855     private static boolean equal(String a, String b) {
 856         return (a == null || b == null ? a == b : a.equals(b));
 857     }
 858 
 859     @Override
 860     public void dispose() {
 861         if (viewConfig != null) {
 862             viewConfig.dispose();
 863             viewConfig = null;
 864         }
 865         super.dispose();
 866     }
 867 
 868     protected void initGUI() {
 869         setHelp("confEdit.window.csh");
 870         listener = new Listener();
 871 
 872         updateTitle();
 873 
 874         if (viewConfig.getHelpSet() != null) {
 875             // would prefer that the helpset came from the test suite
 876             infoPanel = new JHelpContentViewer(Help.getHelpSet(viewConfig));
 877             infoPanel.setName("info");
 878             int dpi = uif.getDotsPerInch();
 879             infoPanel.setPreferredSize(new Dimension(4*dpi, 3*dpi));
 880             infoPanel.putClientProperty(HelpLink.HELPBROKER_FOR_HELPLINK, uif.getHelpBroker());
 881         }
 882 
 883         fullView = new CE_FullView(viewConfig, infoPanel, uif, listener);
 884         if (customRenderersMap != null) {
 885             fullView.setCustomRenderers(customRenderersMap);
 886         }
 887 
 888         stdView = new CE_StdView(viewConfig, infoPanel, uif, listener);
 889         stdView.setParentToolDialog(this);
 890 
 891         initMenuBar();
 892 
 893         views = uif.createPanel("ce.views", new CardLayout(), false);
 894         views.add(fullView, fullView.getName());
 895         views.add(stdView, stdView.getName());
 896 
 897         if (infoPanel == null) {
 898             viewInfoCheckBox.setEnabled(false);
 899             viewInfoCheckBox.setSelected(false);
 900         }
 901         else {
 902             Preferences p = Preferences.access();
 903             boolean prefMoreInfo = p.getPreference(MORE_INFO_PREF, "true").equals("true");
 904             viewInfoCheckBox.setEnabled(true);
 905             viewInfoCheckBox.setSelected(prefMoreInfo);
 906         }
 907 
 908         // set body to be views+info or views
 909         if (viewInfoCheckBox.isSelected()) {
 910             views.setBorder(null);
 911             JSplitPane sp = uif.createSplitPane(JSplitPane.HORIZONTAL_SPLIT, views, infoPanel);
 912             sp.setDividerLocation(views.getPreferredSize().width + sp.getDividerSize());
 913             body = sp;
 914         }
 915         else {
 916             views.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
 917             body = views;
 918         }
 919 
 920         // Don't register "shift alt D" on body, because body might change
 921         // if the more info is opened/closed.
 922         // Instead, register it on views and infoPanel
 923         views.registerKeyboardAction(listener, DETAILS, detailsKey,
 924                                            JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 925         if (infoPanel != null)
 926             infoPanel.registerKeyboardAction(listener, DETAILS, detailsKey,
 927                                                JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 928 
 929         setBody(body);
 930 
 931         setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
 932     }
 933 
 934     protected JMenu createFileMenu() {
 935         String[] fileMenuItems = new String[] { SAVE, SAVE_AS, REVERT, null,
 936             NEW, LOAD, null, CLOSE };
 937         JMenu fileMenu = uif.createMenu("ce.file", fileMenuItems, listener);
 938 
 939         FileHistory h = FileHistory.getFileHistory(viewConfig.getWorkDirectory(), getHistoryFileName());
 940         FileHistory.Listener l = new FileHistory.Listener(h, 0, listener);
 941         recentConfigMenu = uif.createMenu("ce.history");
 942         recentConfigMenu.setEnabled(h.getLatestEntry() != null);
 943         recentConfigMenu.addMenuListener(l);
 944         fileMenu.insert(recentConfigMenu, 4);
 945 
 946         return fileMenu;
 947     }
 948 
 949     private void initMenuBar() {
 950         listener = new Listener();
 951         JMenuBar menuBar = uif.createMenuBar("ce.menub");
 952 
 953         JMenu fileMenu = createFileMenu();
 954         menuBar.add(fileMenu);
 955 
 956         // marker menu
 957         markerMenu = fullView.getMarkerMenu();
 958         menuBar.add(markerMenu);
 959 
 960         // search menu
 961         searchMenu = fullView.getSearchMenu();
 962         menuBar.add(searchMenu);
 963 
 964         // view menu
 965         viewMenu = uif.createMenu("ce.view");
 966         viewMenu.addMenuListener(listener);
 967         ButtonGroup viewGroup = new ButtonGroup();
 968 
 969         viewFullBtn = uif.createRadioButtonMenuItem("ce.view", CE_View.FULL);
 970         viewFullBtn.setSelected(true);
 971         viewFullBtn.setActionCommand(CE_View.FULL);
 972         viewFullBtn.addActionListener(listener);
 973         viewGroup.add(viewFullBtn);
 974         viewMenu.add(viewFullBtn);
 975 
 976         viewStdBtn = uif.createRadioButtonMenuItem("ce.view", CE_View.STD);
 977         viewStdBtn.setActionCommand(CE_View.STD);
 978         viewStdBtn.addActionListener(listener);
 979         viewGroup.add(viewStdBtn);
 980         viewMenu.add(viewStdBtn);
 981 
 982         viewMenu.addSeparator();
 983 
 984         viewInfoCheckBox = uif.createCheckBoxMenuItem("ce.view", "info", false);
 985         viewInfoCheckBox.addChangeListener(listener);
 986         viewMenu.add(viewInfoCheckBox);
 987 
 988         viewTagCheckBox = uif.createCheckBoxMenuItem("ce.view", "tag", false);
 989         viewTagCheckBox.setAccelerator(KeyStroke.getKeyStroke("control T"));
 990         viewTagCheckBox.addChangeListener(listener);
 991         viewMenu.add(viewTagCheckBox);
 992 
 993         viewMenu.addSeparator();
 994 
 995         viewRefreshItem = uif.createMenuItem("ce.view", "refresh", listener);
 996         viewRefreshItem.setAccelerator(KeyStroke.getKeyStroke("F5"));
 997         viewMenu.add(viewRefreshItem);
 998 
 999         menuBar.add(viewMenu);
1000 
1001         menuBar.add(uif.createHorizontalGlue("ce.pad"));
1002 
1003         // help menu
1004         JMenu helpMenu = uif.createMenu("ce.help");
1005         // config editor help
1006         JMenuItem mainItem = uif.createHelpMenuItem("ce.help.main", "confEdit.window.csh");
1007         helpMenu.add(mainItem);
1008 
1009 /**
1010         // template editor help
1011         mainItem = uif.createHelpMenuItem("ce.help.maint", "confEdit.templateDialog.csh");
1012         helpMenu.add(mainItem);
1013  */
1014         JMenuItem fullItem = uif.createHelpMenuItem("ce.help.full", "confEdit.fullView.csh");
1015         helpMenu.add(fullItem);
1016         JMenuItem stdItem = uif.createHelpMenuItem("ce.help.std", "confEdit.stdView.csh");
1017         helpMenu.add(stdItem);
1018         menuBar.add(helpMenu);
1019 
1020         setJMenuBar(menuBar);
1021     }
1022 
1023     protected void updateTitle() {
1024         File f = viewConfig.getFile();
1025         setI18NTitle("ce.title",
1026                     new Object[] { new Integer(currView == fullView ? 0 : 1),
1027                     new Integer(f == null ? 0 : 1), f });
1028     }
1029 
1030     private boolean isInfoVisible() {
1031         return (body instanceof JSplitPane);
1032     }
1033 
1034     private void setInfoVisible(boolean b) {
1035         // verify there is an infoPanel to be made visible
1036         if (infoPanel == null)
1037             throw new IllegalStateException();
1038 
1039         // check if already set as desired
1040         if (b == isInfoVisible())
1041             return;
1042 
1043         // get dimensions of views and info panel
1044         Dimension viewsSize = views.getSize();
1045         if (viewsSize.width == 0)
1046             viewsSize = views.getPreferredSize();
1047 
1048         Dimension infoSize = infoPanel.getSize();
1049         if (infoSize.width == 0)
1050             infoSize = infoPanel.getPreferredSize();
1051 
1052 
1053         if (b) {
1054             // set body to views+info; remove border, because JSplitPane adds in
1055             // its own padding
1056             views.setBorder(null);
1057             JSplitPane sp = uif.createSplitPane(JSplitPane.HORIZONTAL_SPLIT, views, infoPanel);
1058             sp.setDividerLocation(viewsSize.width + sp.getDividerSize());
1059             body = sp;
1060             showInfoForQuestion(viewConfig.getCurrentQuestion());
1061         }
1062         else {
1063             // set body to views; add a border to stand in for the padding
1064             // that JSplitPane would otherwise give
1065             views.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
1066             body = views;
1067         }
1068 
1069         setBody(body);
1070         if (isShowing()) {
1071             // adjust the size of the window up or down as appropriate
1072             Dimension winSize = getSize();
1073             int divWidth = new JSplitPane().getDividerSize();
1074             int newWidth = winSize.width;
1075             newWidth += (b ? +1 : -1) * (infoSize.width + divWidth);
1076             setSize(newWidth, winSize.height);
1077         }
1078     }
1079 
1080     private void showInfoForQuestion(Question q) {
1081         HelpID helpId = Help.getHelpID(q);
1082         // uugh
1083         if (helpId == null)
1084             System.err.println("WARNING: no help for " + q.getKey());
1085         else
1086             infoPanel.setCurrentID(helpId);
1087     }
1088 
1089 
1090     private CE_View getDefaultView() {
1091         if (currView != null)
1092             return currView;
1093         Preferences p = Preferences.access();
1094         String prefView = p.getPreference(VIEW_PREF, CE_View.FULL);
1095         if (prefView.equals(CE_View.STD))
1096             return stdView;
1097         else
1098             return fullView;
1099     }
1100 
1101     protected void perform(String cmd) {
1102         if (cmd.equals(NEW))
1103             newConfigAsk();
1104         else if (cmd.equals(LOAD))
1105             loadConfigAsk();
1106 /*
1107         else if (cmd.equals(LOADT))
1108             load(true);
1109         else if (cmd.equals(NEWT))
1110             clear(true);
1111  */
1112         else if (cmd.equals(SAVE))
1113             save();
1114         else if (cmd.equals(SAVE_AS))
1115             saveAs();
1116         else if (cmd.equals(REVERT))
1117             revert();
1118         else if (cmd.equals(CE_View.FULL))
1119             show(fullView);
1120         else if (cmd.equals(CE_View.STD))
1121             show(stdView);
1122         else if (cmd.equals(CLOSE)) {
1123             close();
1124         }
1125         else if (cmd.equals(DONE)) {
1126             if (currView != null && !currView.isOKToClose())
1127                 return;
1128 
1129             if (!canInterruptTemplateCreation() && !viewConfig.isFinishable()) {
1130                 uif.showError("ce.force_close");
1131                 return;
1132             }
1133 
1134             currView.save();
1135             if (!viewConfig.isFinishable()) {
1136                 Integer rp = new Integer(runPending ? 1 : 0);
1137                 int rc = uif.showOKCancelDialog("ce.okToClose", rp);
1138                 if (rc != JOptionPane.OK_OPTION)
1139                     return;
1140             }
1141 
1142             if (isEdited() || isCurrentQuestionChanged())
1143                 saveRequired = true;
1144 
1145             if (saveRequired) {
1146                 if (!save0()) {
1147                     // save failed, stay in CE, don't clear saveRequired flag
1148                     return;
1149                 }
1150 
1151                 // save succeeded, so safe to clear saveRequired flag
1152                 saveRequired = false;
1153             }
1154 
1155             close(false);
1156         }
1157         else if (cmd.equals(REFRESH)) {
1158             if (currView != null)
1159                 currView.refresh();
1160         }
1161         else if (cmd.equals(DETAILS)) {
1162             if (detailsBrowser == null) {
1163                 detailsBrowser = new DetailsBrowser(body, viewConfig, infoPanel);
1164                 detailsBrowser.setQuestionInfoEnabled(currView == fullView);
1165             }
1166 
1167             detailsBrowser.setVisible(true);
1168         }
1169         else
1170             throw new IllegalArgumentException(cmd);
1171     }
1172 
1173     private boolean canInterruptTemplateCreation () {
1174         /** fa
1175         ContextManager cm = getContextManager();
1176         String wdTmpl = TemplateUtilities.getTemplatePath(model.getWorkDirectory());
1177         if (mainConfig.isTemplate() &&
1178                 !cm.getFeatureManager().isEnabled(FeatureManager.WD_WITHOUT_TEMPLATE) &&
1179                 wdTmpl == null) {
1180             return false;
1181         }
1182          */
1183         return true;
1184     }
1185 
1186     public static void copy(InterviewParameters from, InterviewParameters to)
1187         throws Interview.Fault
1188     {
1189         copy(from, to, true); // copy filename as well, by default
1190     }
1191 
1192     private static void copy(InterviewParameters from, InterviewParameters to,
1193                       boolean copyFile)
1194         throws Interview.Fault
1195     {
1196         //System.err.println("CE.copy from " + (from==mainConfig?"main":from==viewConfig?"view":from.toString()) + " to " + (to==mainConfig?"main":to==viewConfig?"view":to.toString()));
1197         Map<String, String> data = new HashMap<>();
1198         from.save(data);
1199         to.load(data, false);
1200         to.setTemplate(from.isTemplate());
1201 
1202         if (copyFile)
1203             to.setFile(from.getFile());
1204         if (debug) {
1205             Debug.println("InterviewEditor: equal(b,a) " + equal(to,from));
1206         }
1207 
1208     }
1209 
1210     /**
1211     * Checks default settings relate to config file load fron the default location
1212     * @param cm <code>ContextManager</code> object defining current harness' context. The following methods
1213     *           affect this method functionality:
1214     * <ul>
1215     * <li><code>getDefaultConfigLoadPath()</code>
1216     * <li><code>getAllowConfigLoadOutsideDefault()</code>
1217     * </ul>
1218     * @throws <code>IllegalArgumentException</code> if the following configuration errors found:
1219     * <ul>
1220     * <li> <code>getDefaultConfigLoadPath()</code> returns <code>null</code> when <code>getAllowConfigLoadOutsideDefault()</code> returns <code>false</code>
1221     * <li> <code>getDefaultConfigLoadPath()</code> returns not absolute path
1222     * <li> <code>getDefaultConfigLoadPath()</code> returns a file (not a directory)
1223     * </ul>
1224     * @see ContextManager#setDefaultConfigLoadPath(java.io.File)
1225     * @see ContextManager#setAllowConfigLoadOutsideDefault(boolean state)
1226     * @see ContextManager#getDefaultConfigLoadPath()
1227     * @see ContextManager#getAllowConfigLoadOutsideDefault()
1228     */
1229 
1230     public static File checkLoadConfigFileDefaults(ContextManager cm) {
1231         if (cm == null)
1232             return null;
1233 
1234         File defaultConfigLoadPath = cm.getDefaultConfigLoadPath();
1235         boolean allowConfigLoadOutsideDefault = cm.getAllowConfigLoadOutsideDefault();
1236 
1237         if (defaultConfigLoadPath == null && !allowConfigLoadOutsideDefault)
1238             throw new IllegalArgumentException("Default directory not specified for " +
1239                 "load operation when allowConfigLoadOutsideDefault is false");
1240 
1241         if (defaultConfigLoadPath != null) {
1242             if (!defaultConfigLoadPath.isAbsolute())
1243                 throw new IllegalArgumentException("Relative paths not " +
1244                     "currently supported. The following setting is incorrect: " +
1245                     "\"" + defaultConfigLoadPath.getPath() + "\" selected for " +
1246                     "load operation");
1247 
1248             if (defaultConfigLoadPath.isFile())
1249                 throw new IllegalArgumentException("Filename selected unexpectedly " +
1250                     "as a default directory: " +
1251                     "\"" + defaultConfigLoadPath.getPath() + "\" for " +
1252                     "load operation");
1253         }
1254 
1255         return defaultConfigLoadPath;
1256     }
1257 
1258     static File loadConfigFile(ContextManager cm, Component parent, UIFactory uif, String ext, String key) {
1259         FileChooser fileChooser = new FileChooser(true);
1260         fileChooser.addChoosableExtension(ext, uif.getI18NString("ce.jtiFiles" + key));
1261         return loadConfigFile(cm, parent, uif, fileChooser);
1262     }
1263 
1264     /**
1265     * Provides capabilities for configuration file loading. Method takes into
1266     * account context settings relating to default locations for configuration
1267     * files loading and behaves according to them.
1268     * @param cm <code>ContextManager</code> object defining current harness' context. The following methods
1269     *           affect this method functionality:
1270     * <li><code>getDefaultConfigLoadPath()</code>
1271     * <li><code>getAllowConfigLoadOutsideDefault()</code>
1272     * </ul>
1273     * @param parent A parent frame to be used for <code>fileChooser</code>/warning dialogs
1274     * @param uif The UIFactory used to for configuration file loading operation
1275     * @param fileChooser The <code>FileChooser</code> used for configuration file loading
1276     * @return The configuration file selected by user if this file loading is allowed by
1277     *         harness' contest settings
1278     * @see ContextManager#setDefaultConfigLoadPath(java.io.File)
1279     * @see ContextManager#setAllowConfigLoadOutsideDefault(boolean state)
1280     * @see ContextManager#getDefaultConfigLoadPath()
1281     * @see ContextManager#getAllowConfigLoadOutsideDefault()
1282     */
1283 
1284     static File loadConfigFile(ContextManager cm, Component parent, UIFactory uif, FileChooser fileChooser) {
1285 
1286         if (cm == null)
1287             return null;
1288 
1289         File defaultConfigLoadPath = checkLoadConfigFileDefaults(cm);
1290         boolean allowConfigLoadOutsideDefault = cm.getAllowConfigLoadOutsideDefault();
1291 
1292         File file = null;
1293 
1294         fileChooser.setDialogTitle(uif.getI18NString("ce.load.title"));
1295 
1296         if (defaultConfigLoadPath != null) {
1297             if (!allowConfigLoadOutsideDefault) {
1298                 if (!(new File(defaultConfigLoadPath.getAbsolutePath())).canRead()) {
1299                     uif.showError("ce.load.defDirNotExists", defaultConfigLoadPath);
1300                     return null;
1301                 }
1302                 fileChooser.enableDirectories(false);
1303             } else
1304                 fileChooser.enableDirectories(true);
1305             fileChooser.setCurrentDirectory(defaultConfigLoadPath);
1306         }
1307 
1308         boolean isMatch = true;
1309 
1310         while (file == null) {
1311             int rc = fileChooser.showDialog(parent, uif.getI18NString("ce.load.btn"));
1312             if (rc != JFileChooser.APPROVE_OPTION)
1313                 return null;
1314 
1315             file = fileChooser.getSelectedFile();
1316 
1317             if (!allowConfigLoadOutsideDefault) {
1318                 if (defaultConfigLoadPath == null)
1319                     return null;
1320 
1321                 File f = new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().lastIndexOf(File.separator)));
1322 
1323                 try {
1324                     isMatch = (f.getCanonicalPath().indexOf((defaultConfigLoadPath.getCanonicalPath())) == 0);
1325                 } catch ( IOException ioe) {
1326                     ioe.printStackTrace(System.err);
1327                     return null;
1328                 }
1329 
1330                 if (!isMatch) {
1331                     uif.showError("ce.load.notAllowedDir", defaultConfigLoadPath);
1332 
1333 
1334                     file = null;
1335                     fileChooser.setCurrentDirectory(defaultConfigLoadPath);
1336                     continue;  // choose another file
1337                 }
1338             }
1339         }
1340 
1341         if (file != null) {
1342             String path = file.getPath();
1343             String ext = fileChooser.getChosenExtension();
1344             if (ext == null) {
1345                 ext = CONFIG_EXTENSION;
1346             }
1347             if (!path.endsWith(ext))
1348                 file = new File(path + ext);
1349         }
1350 
1351         return file;
1352     }
1353 
1354     /**
1355     * Provides as the user with a dialog to chooser where to save a config. Method takes into account
1356     * context settings relating to default locations for configuration files saving and behaves
1357     * according to them.
1358     * @param cm <code>ContextManager</code> object defining current harness' context. The following methods
1359     *           affect this method functionality:
1360     * <ul>
1361     * <li><code>getDefaultConfigSavePath()</code>
1362     * <li><code>getAllowConfigSaveOutsideDefault()</code>
1363     * </ul>
1364     * @param parent A parent frame to be used for <code>fileChooser</code>/warning dialogs
1365     * @param uif The UIFactory used to for configuration file saving operation
1366     * @param fileChooser The <code>FileChooser</code> used for configuration file saving
1367     * @return The configuration file selected by user if this file saving is allowed by
1368     *         harness' contest settings
1369     * @throws <code>IllegalArgumentException</code> if the following configuration errors found:
1370     * <ul>
1371     * <li> <code>getDefaultConfigSavePath()</code> returns <code>null</code> when <code>getAllowConfigSaveOutsideDefault()</code> returns <code>false</code>
1372     * <li> <code>getDefaultConfigSavePath()</code> returns not absolute path
1373     * <li> <code>getDefaultConfigSavePath()</code> returns a file (not a directory)
1374     * </ul>
1375     * @see ContextManager#setDefaultConfigSavePath(java.io.File)
1376     * @see ContextManager#setAllowConfigSaveOutsideDefault(boolean state)
1377     * @see ContextManager#getDefaultConfigSavePath()
1378     * @see ContextManager#getAllowConfigSaveOutsideDefault()
1379     */
1380 
1381     static File saveConfigFile(ContextManager cm, Component parent, UIFactory uif, FileChooser fileChooser, File dir,
1382             boolean isTemplate) {
1383         if (cm == null)
1384             return null;
1385 
1386         File defaultSavePath;
1387         if (isTemplate) {
1388             defaultSavePath = cm.getDefaultTemplateSavePath();
1389         } else {
1390             defaultSavePath = cm.getDefaultConfigSavePath();
1391         }
1392         boolean allowSaveOutsideDefault;
1393         if (isTemplate) {
1394             allowSaveOutsideDefault = cm.getAllowTemplateSaveOutsideDefault();
1395         } else {
1396             allowSaveOutsideDefault = cm.getAllowConfigSaveOutsideDefault();
1397         }
1398 
1399 
1400         if (defaultSavePath == null && !allowSaveOutsideDefault)
1401             throw new IllegalArgumentException("Default directory not specified for " +
1402                 "save operation when allowConfigSaveOutsideDefault is false");
1403 
1404         if (defaultSavePath != null) {
1405             if (!defaultSavePath.isAbsolute())
1406                 throw new IllegalArgumentException("Relative paths not " +
1407                     "currently supported. The following setting is incorrect: " +
1408                     "\"" + defaultSavePath.getPath() + "\" selected for " +
1409                     "save operation");
1410 
1411             if (defaultSavePath.isFile())
1412                 throw new IllegalArgumentException("Filename selected unexpectedly " +
1413                     "as a default directory: " +
1414                     "\"" + defaultSavePath.getPath() + "\" for " +
1415                     "save operation");
1416 
1417             if (!allowSaveOutsideDefault) {
1418                 if (!defaultSavePath.canWrite()) {
1419                     uif.showError("ce.save.defDirNotExists", defaultSavePath);
1420                     return null;
1421                 }
1422                 fileChooser.enableDirectories(false);
1423             } else
1424                 fileChooser.enableDirectories(true);
1425 
1426             fileChooser.setCurrentDirectory(defaultSavePath);
1427         } else
1428             if (dir != null)
1429                 fileChooser.setCurrentDirectory(dir);
1430 
1431         File file = null;
1432         boolean isMatch = true;
1433 
1434         while (file == null) {
1435             int rc = fileChooser.showDialog(parent, uif.getI18NString("ce.save.btn"));
1436             if (rc != JFileChooser.APPROVE_OPTION)
1437                 // user has canceled or closed the chooser
1438                 return null;
1439 
1440             file = fileChooser.getSelectedFile();
1441             if (file == null) // just making sure
1442                 continue;
1443 
1444             File f = new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().lastIndexOf(File.separator)));
1445 
1446             if (!allowSaveOutsideDefault) {
1447                 if (defaultSavePath == null)
1448                     return null;
1449 
1450                 try {
1451                     isMatch = defaultSavePath.getCanonicalPath().equals(f.getCanonicalPath());
1452                 } catch ( IOException ioe) {
1453                     ioe.printStackTrace(System.err);
1454                     return null;
1455                 }
1456 
1457                 if (!isMatch) {
1458                     uif.showError("ce.save.notAllowedDir", defaultSavePath);
1459                     file = null;
1460                     fileChooser.setCurrentDirectory(defaultSavePath);
1461                     continue;  // choose another file
1462                 }
1463             }
1464 
1465             if (file.isDirectory()) {
1466                 uif.showError("ce.save.fileIsDir", file);
1467                 file = null;
1468                 continue;  // choose another file
1469             }
1470 
1471             File parentFile = file.getParentFile();
1472             if (parentFile != null) {
1473                 if (parentFile.exists() && !parentFile.isDirectory()) {
1474                     uif.showError("ce.save.parentNotADir", parentFile);
1475                     file = null;
1476                     continue;  // choose another file
1477                 } else if (!parentFile.exists()) {
1478                     rc = uif.showYesNoDialog("ce.save.createParentDir",
1479                                              parentFile);
1480                     if (rc == JOptionPane.YES_OPTION) {
1481                         if (!parentFile.mkdirs()) {
1482                              uif.showError("ce.save.cantCreateParentDir",
1483                                            parentFile);
1484                              file = null;
1485                              continue;  // choose another file
1486                         }
1487                     } else {
1488                         file = null;
1489                         continue;  // choose another file
1490                     }
1491                 }
1492             }
1493 
1494             // if file exists, leave well enough alone;
1495             // otherwise, make sure it ends with .jti or .jtm
1496             if (!file.exists()) {
1497                 String path = file.getPath();
1498                 String ext = fileChooser.getChosenExtension();
1499                 if (ext != null && !path.endsWith(ext))
1500                     file = new File(path + ext);
1501             }
1502 
1503             // if file exists, make sure user wants to overwrite it
1504             if (file.exists()) {
1505                 rc = uif.showYesNoDialog("ce.save.warn");
1506                 switch (rc) {
1507                     case JOptionPane.YES_OPTION:
1508                         break;  // use this file
1509 
1510                     case JOptionPane.NO_OPTION:
1511                         fileChooser.setSelectedFile(null);
1512                         file = null;
1513                         continue;  // choose another file
1514                 }
1515             }
1516         }
1517         return file;
1518     }
1519 
1520     void setAfterCloseCommand(Runnable runnable) {
1521         afterCloseCommand = runnable;
1522     }
1523 
1524     private Runnable afterCloseCommand;
1525 
1526     /**
1527      * Will be eliminated in the next release.
1528      * @deprecated
1529      */
1530     @Deprecated
1531     protected boolean templateMode = false;
1532     protected ContextManager contextManager = null;
1533     protected InterviewParameters mainConfig;
1534     protected InterviewParameters viewConfig;
1535     private FileHistory history;
1536     private boolean saveRequired;
1537     //protected String key;
1538 
1539     private boolean runPending;
1540     private static boolean debug = Debug.getBoolean(InterviewEditor.class);
1541 
1542 
1543 
1544     private JMenu recentConfigMenu;
1545     private JMenu markerMenu;
1546     private JMenu searchMenu;
1547     private JMenu viewMenu;
1548     private JRadioButtonMenuItem viewFullBtn;
1549     private JRadioButtonMenuItem viewStdBtn;
1550     private JCheckBoxMenuItem viewInfoCheckBox;
1551     private JCheckBoxMenuItem viewTagCheckBox;
1552     private JMenuItem viewRefreshItem;
1553     private JComponent body;
1554     private JPanel views;
1555     private JHelpContentViewer infoPanel;
1556     private CE_FullView fullView;
1557     private CE_StdView stdView;
1558     private CE_View currView;
1559     private Listener listener;
1560     //private TemplatesUI templatesUI;
1561 
1562     private Map<Class<? extends Question>, QuestionRenderer> customRenderersMap;
1563     private ActionListener closeListener;
1564     //private ExecModel model;
1565     private final List<Observer> observers = new ArrayList<Observer>();
1566 
1567     private DetailsBrowser detailsBrowser;
1568     private static final KeyStroke detailsKey = KeyStroke.getKeyStroke("shift alt D");
1569     protected String ext;
1570 
1571     // XXX this isn't the right class to define these in
1572     //     do not make more public than package private
1573     static final String CONFIG_EXTENSION = ".jti";
1574     static final String CONFIG_HISTORY = "configHistory.jtl";
1575 
1576     private static final String NEW = "new";
1577     private static final String LOAD = "load";
1578     //private static final String NEWT = "newt";
1579     //private static final String LOADT = "loadt";
1580     private static final String SAVE = "save";
1581     private static final String SAVE_AS = "saveAs";
1582     private static final String REVERT = "revert";
1583     private static final String DONE = "done";
1584     private static final String REFRESH = "refresh";
1585     private static final String DETAILS = "details";
1586             static final String CLOSE = "close";
1587 
1588     static final String MORE_INFO_PREF = "exec.config.moreInfo";
1589     static final String VIEW_PREF = "exec.config.view";
1590 
1591 
1592     public void setCustomRenderers(Map<Class<? extends Question>, QuestionRenderer> renderersMap) {
1593         customRenderersMap = renderersMap;
1594         if (fullView != null) {
1595             fullView.setCustomRenderers(customRenderersMap);
1596         }
1597     }
1598 
1599     private class Listener
1600         implements ActionListener, ChangeListener, MenuListener
1601     {
1602         // ---------- from ActionListener -----------
1603 
1604         public void actionPerformed(ActionEvent e) {
1605             Object src = e.getSource();
1606             if (src instanceof JMenuItem) {
1607                 JMenuItem mi = (JMenuItem) src;
1608                 File f = (File) (mi.getClientProperty(FileHistory.FILE));
1609                 if (f != null && askAndSave("ce.load.warn")) {
1610                     loadConfigFromFile(f);
1611                     return;
1612                 }
1613             }
1614             perform(e.getActionCommand());
1615         }
1616 
1617         // ---------- from ChangeListener -----------
1618 
1619         public void stateChanged(ChangeEvent e) {
1620             Object src = e.getSource();
1621             if (src == viewInfoCheckBox && infoPanel != null)
1622                 setInfoVisible(viewInfoCheckBox.isSelected());
1623             else if (src == viewTagCheckBox)
1624                 fullView.setTagVisible(viewTagCheckBox.isSelected());
1625         }
1626 
1627         // ---------- from MenuListener -----------
1628 
1629         public void menuSelected(MenuEvent e) {
1630             Object src = e.getSource();
1631             if (src == viewMenu)
1632                 viewTagCheckBox.setSelected(fullView.isTagVisible());
1633         }
1634 
1635         public void menuDeselected(MenuEvent e) {
1636         }
1637 
1638         public void menuCanceled(MenuEvent e) {
1639         }
1640 
1641     };
1642 
1643     /**
1644      * For private communication with SessionControl, not for broadcast outside
1645      * of core JT.
1646      */
1647     public interface Observer {
1648         /**
1649          * Invoked when value of interview parameters has been changed
1650          * @param p object with updated value (viewConfig)
1651          */
1652         public void changed(InterviewParameters p);
1653 
1654         /**
1655          * Invoked when setVisible() method is invoked on InterviewEditor object
1656          * @param isVisible argument passed to setVisible() method
1657          * @param source editor that changed the state
1658          */
1659         public void changedVisibility(boolean isVisible, InterviewEditor source);
1660     }
1661 }