1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2001, 2015, 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;
  28 
  29 import java.io.BufferedInputStream;
  30 import java.io.BufferedOutputStream;
  31 import java.io.File;
  32 import java.io.FileInputStream;
  33 import java.io.FileNotFoundException;
  34 import java.io.FileOutputStream;
  35 import java.io.IOException;
  36 import java.io.InputStream;
  37 import java.io.OutputStream;
  38 import java.util.*;
  39 
  40 import com.sun.interview.ErrorQuestion;
  41 import com.sun.interview.FinalQuestion;
  42 import com.sun.interview.Interview;
  43 import com.sun.interview.Question;
  44 import com.sun.javatest.tool.CustomPropagationController;
  45 import com.sun.javatest.tool.FileHistory;
  46 import com.sun.javatest.util.BackupPolicy;
  47 import com.sun.javatest.util.I18NResourceBundle;
  48 import com.sun.javatest.util.Properties;
  49 
  50 /**
  51  * Configuration parameters provided via an interview.
  52  *
  53  * @since 3.0
  54  */
  55 public abstract class InterviewParameters
  56     extends Interview
  57     implements Parameters
  58 {
  59 
  60     /**
  61      * Indicates problems when accessing the work directory.
  62      */
  63     public static class WorkDirFault extends Interview.Fault {
  64         /**
  65          * Create a fault with an internationalized message.
  66          * @param i18n The bundle from which to get the string.
  67          * @param s The key for getting the string to be displayed from the
  68          *          supplied bundle.
  69          */
  70         public WorkDirFault(ResourceBundle i18n, String s) {
  71             super(i18n, s);
  72         }
  73 
  74         /**
  75          * Create a fault with an internationalized message.
  76          * @param i18n The bundle from which to get the string.
  77          * @param s The key for getting the string to be displayed from the
  78          *          supplied bundle.
  79          * @param o Parameter to use when resolving the string from the bundle.
  80          * @see java.text.MessageFormat
  81          */
  82         public WorkDirFault(ResourceBundle i18n, String s, Object o) {
  83             super(i18n, s, o);
  84         }
  85 
  86         /**
  87          * Create a fault with an internationalized message.
  88          * @param i18n The bundle from which to get the string.
  89          * @param s The key for getting the string to be displayed from the
  90          *          supplied bundle.
  91          * @param o Parameters to use when resolving the string from the bundle.
  92          * @see java.text.MessageFormat
  93          */
  94         public WorkDirFault(ResourceBundle i18n, String s, Object[] o) {
  95             super(i18n, s, o);
  96         }
  97     }
  98 
  99     /**
 100      * Indicates problems when accessing the test suite.
 101      */
 102     public static class TestSuiteFault extends Interview.Fault {
 103         /**
 104          * Create a fault with an internationalized message.
 105          * @param i18n The bundle from which to get the string.
 106          * @param s The key for getting the string to be displayed from the
 107          *          supplied bundle.
 108          */
 109         public TestSuiteFault(ResourceBundle i18n, String s) {
 110             super(i18n, s);
 111         }
 112 
 113         /**
 114          * Create a fault with an internationalized message.
 115          * @param i18n The bundle from which to get the string.
 116          * @param s The key for getting the string to be displayed from the
 117          *          supplied bundle.
 118          * @param o Parameter to use when resolving the string from the bundle.
 119          * @see java.text.MessageFormat
 120          */
 121         public TestSuiteFault(ResourceBundle i18n, String s, Object o) {
 122             super(i18n, s, o);
 123         }
 124 
 125         /**
 126          * Create a fault with an internationalized message.
 127          * @param i18n The bundle from which to get the string.
 128          * @param s The key for getting the string to be displayed from the
 129          *          supplied bundle.
 130          * @param o Parameters to use when resolving the string from the bundle.
 131          * @see java.text.MessageFormat
 132          */
 133         public TestSuiteFault(ResourceBundle i18n, String s, Object[] o) {
 134             super(i18n, s, o);
 135         }
 136     }
 137 
 138     /**
 139      * Indicates problems when accessing the configuration file.
 140      */
 141     public static class JTIFault extends Interview.Fault {
 142         /**
 143          * Create a fault with an internationalized message.
 144          * @param i18n The bundle from which to get the string.
 145          * @param s The key for getting the string to be displayed from the
 146          *          supplied bundle.
 147          */
 148         public JTIFault(ResourceBundle i18n, String s) {
 149             super(i18n, s);
 150         }
 151 
 152         /**
 153          * Create a fault with an internationalized message.
 154          * @param i18n The bundle from which to get the string.
 155          * @param s The key for getting the string to be displayed from the
 156          *          supplied bundle.
 157          * @param o Parameter to use when resolving the string from the bundle.
 158          * @see java.text.MessageFormat
 159          */
 160         public JTIFault(ResourceBundle i18n, String s, Object o) {
 161             super(i18n, s, o);
 162         }
 163 
 164         /**
 165          * Create a fault with an internationalized message.
 166          * @param i18n The bundle from which to get the string.
 167          * @param s The key for getting the string to be displayed from the
 168          *          supplied bundle.
 169          * @param o Parameters to use when resolving the string from the bundle.
 170          * @see java.text.MessageFormat
 171          */
 172         public JTIFault(ResourceBundle i18n, String s, Object[] o) {
 173             super(i18n, s, o);
 174         }
 175     }
 176 
 177     /**
 178      *  The template manager is used to change behavior of
 179      *  template saving, the default implementation is
 180      *  the context manager of corresponding test suite.
 181      */
 182     public interface TemplateManager {
 183         /**
 184          * This method is invoked each time before saving template.
 185          * The template will be saved only if this method returns true.
 186          * @param file template file
 187          * @return true if this operation is allowed, false otherwise
 188          */
 189         public boolean canSaveTemplate(File file);
 190     }
 191 
 192     public void setPropagationController(CustomPropagationController pc) {
 193         this.pc = pc;
 194      }
 195 
 196     public CustomPropagationController getPropagationController() {
 197         return pc;
 198     }
 199 
 200     private TemplateManager templateManager = null;
 201     /**
 202      * Create an InterviewParameters object.
 203      * @param tag The tag used to qualify questions in this interview
 204      */
 205     protected InterviewParameters(String tag) {
 206         super(tag);
 207     }
 208 
 209     /**
 210      * Set the work directory to be used for this test run.
 211      * @param workDir the work directory to be used for this test run.
 212      * It must match the test suite to be used for this test run
 213      */
 214     public abstract void setWorkDirectory(WorkDirectory workDir);
 215 
 216     /**
 217      * Set given template manager for this InterviewParameters.
 218      * @param tm new template manager
 219      */
 220     public void setTemplateManger(TemplateManager tm) {
 221         this.templateManager = tm;
 222     }
 223 
 224     /**
 225      * Return the template manager for this InterviewParameters.
 226      */
 227     public TemplateManager getTemplateManger() {
 228         return templateManager;
 229     }
 230 
 231     /**
 232      * Initialize an InterviewParameters object.
 233      * This method is called when the object is created
 234      * from an entry in a .jtt file.
 235      * By default, the method throws an exception if any arguments
 236      * are given. It should be redefined by any test suites that wish
 237      * to support this type of initialization.
 238      * @param args test suite specific args with which to initialize
 239      * this InterviewParameters object
 240      * @throws Interview.Fault if any problems occurred while processing the arguments
 241      */
 242     public void init(String[] args) throws Fault {
 243         if (args != null && args.length > 0)
 244             throw new Fault(i18n, "ip.unknownArgs");
 245     }
 246 
 247     /**
 248      * Clean up an InterviewParameters object.
 249      * This method should be invoked at the moment InterviewParameters object
 250      * becomes useless by the code, that controls it's lifecycle. For example,
 251      * at the end of the method which created it's local instance.
 252      *
 253      * Any following invocations on this object may result in unpredictable
 254      * exceptions because of object inconsistence.
 255      */
 256     public void dispose() {
 257         kflFiles = null;
 258         backupPolicy = null;
 259         cachedExcludeListFilter = null;
 260         cachedKeywordsFilter = null;
 261         cachedRelevantTestFilter = null;
 262         cachedRelevantTestFilterEnv = null;
 263         cachedStatusFilter = null;
 264         cachedTestFilters = null;
 265         pc = null;
 266         templateManager = null;
 267     }
 268 
 269     //----------------------------------------------------------------------------
 270 
 271     /**
 272      * Get the name for this configuration.
 273      * By default and for backwards compatibility, this defaults to the
 274      * name of the test environment, which means that the whole environment
 275      * may need to be evaluated to get the required value. Subtypes may
 276      * choose to override this method to provide a more efficient
 277      * implementation.
 278      *
 279      * <p> Since the default implementation gets the name from the test
 280      * environment, clients should not use this method to determine
 281      * the name for the test environment, unless this method is redefined.
 282      * The default implementation detects such a circular usage, and
 283      * returns null in this case.
 284      *
 285      * @return the name for this configuration, or null if not known.
 286      */
 287     public synchronized String getName() {
 288         if (inGetName)
 289             return null;
 290 
 291         try {
 292             inGetName = true;
 293 
 294             EnvParameters eParams = getEnvParameters();
 295             if (eParams != null) {
 296                 // getName to get the name for the environment
 297                 TestEnvironment e = eParams.getEnv();
 298                 if (e != null)
 299                     return e.getName();
 300             }
 301             return null;
 302         }
 303         finally {
 304             inGetName = false;
 305         }
 306     }
 307 
 308     private boolean inGetName;
 309 
 310     /**
 311      * Get a description for this configuration.
 312      * By default and for backwards compatibility, this defaults to the
 313      * description entry in the test environment, which means that the
 314      * whole environment may need to be evaluated to get the required value.
 315      * Subtypes may choose to override this method to provide a more efficient
 316      * implementation.
 317      * @return a description for this configuration, or null if not known
 318      */
 319     public String getDescription() {
 320         EnvParameters eParams = getEnvParameters();
 321         if (eParams != null) {
 322             TestEnvironment e = eParams.getEnv();
 323             if (e != null)
 324                 return e.getDescription();
 325         }
 326         return null;
 327     }
 328 
 329 
 330     //----------------------------------------------------------------------------
 331 
 332     /**
 333      * Get the next question to the asked after the initial prolog
 334      * of questions.
 335      * The default value is the result of getEnvFirstQuestion.
 336      * @return the next question to be asked after the initial prolog
 337      * of questions.
 338      * @see #setFirstQuestion
 339      */
 340     protected Question getPrologSuccessorQuestion() {
 341         return getEnvFirstQuestion();
 342     }
 343 
 344     //----------------------------------------------------------------------------
 345 
 346 
 347     public TestEnvironment getEnv() {
 348         EnvParameters eParams = getEnvParameters();
 349         if (eParams == null)
 350             throw new NullPointerException();
 351         else
 352             return eParams.getEnv();
 353     }
 354 
 355     /**
 356      * Get the first question to be asked concerning the environment to be
 357      * set up and used for each test to be run. If these questions are
 358      * contained in an interview, this method can be simply implemented as:<br>
 359      *  <code>return callInterview(</code><i>envInterview</i><code>, getEnvSuccessorQuestion);</code><br>
 360      * @return the first question to be asked concerning the environment to be
 361      * set up and used for each test to be run.
 362      * @see #getEnvSuccessorQuestion
 363      */
 364     protected abstract Question getEnvFirstQuestion();
 365 
 366     /**
 367      * Get the next question to be asked after those concerning
 368      * the environment to be set up and used for each test to be run.
 369      * The default value is the result of getTestsFirstQuestion.
 370      * @return the next question to be asked after those concerning
 371      * the environment to be set up and used for each test to be run.
 372      * @see #getEnvFirstQuestion
 373      */
 374     protected Question getEnvSuccessorQuestion() {
 375         return getTestsFirstQuestion();
 376     }
 377 
 378     public String[] getTests() {
 379         TestsParameters iParams = getTestsParameters();
 380         return (iParams == null ? null : iParams.getTests());
 381     }
 382 
 383     /**
 384      * Get the first question to be asked concerning the set of tests
 385      * and folders of tests to be run.
 386      * @return the first question to be asked concerning the set of tests
 387      * and folders of tests to be run.
 388      * @see #getTestsSuccessorQuestion
 389      */
 390     protected abstract Question getTestsFirstQuestion();
 391 
 392     /**
 393      * Get the next question to be asked after those concerning
 394      * the tests and folders of tests to be run.
 395      * The default value is the result of getExcludeListFirstQuestion.
 396      * @return the next question to be asked after those concerning
 397      * the tests and folders of tests to be run.
 398      * @see #getTestsFirstQuestion
 399      */
 400     protected Question getTestsSuccessorQuestion() {
 401         return getExcludeListFirstQuestion();
 402     }
 403 
 404     public ExcludeList getExcludeList() {
 405         ExcludeListParameters eParams = getExcludeListParameters();
 406         return (eParams == null ? new ExcludeList() : eParams.getExcludeList());
 407     }
 408 
 409     /**
 410      * Get the combined known failures list.
 411      * Interviews expecting to use known failures lists should generally override
 412      * this method and add support for users to change it.
 413      * @since 4.4
 414      * @see #setKnownFailureFiles(java.io.File[])
 415      * @see com.sun.javatest.interview.BasicInterviewParameters
 416      * @return Current known failures list - combined from the one or more
 417      *    file specified by the user.
 418      */
 419     public KnownFailuresList getKnownFailuresList() {
 420         try {
 421             if (kflFiles != null) {
 422             return new KnownFailuresList(getKnownFailureFiles());
 423         }
 424             else {
 425                 return null;
 426             }
 427 
 428         }
 429         catch (IOException e){
 430             return null;
 431         }
 432         catch (KnownFailuresList.Fault f) {
 433             // report it?
 434             return null;
 435         }
 436     }
 437 
 438     /**
 439      * Set the set of KFL files.
 440      * @since 4.4
 441      * @param files The known failures list files.  The array should contain
 442      *     one or more elements.
 443      */
 444     public void setKnownFailureFiles(File[] files) {
 445         kflFiles = files;
 446     }
 447 
 448     /**
 449      * Get the current set of known failures list files.
 450      * The default implementation will return the value in the kflFiles
 451      * field, which subclasses may set.
 452      * @since 4.4
 453      * @see #setKnownFailureFiles(java.io.File[])
 454      * @return The list of known failure list files.  Null if none.
 455      */
 456     public File[] getKnownFailureFiles() {
 457         return kflFiles;
 458     }
 459 
 460     /**
 461      * Get the first question to be asked concerning the exclude list
 462      * to be used to exclude tests from the test run.
 463      * @return the first question to be asked concerning the exclude list
 464      * to be used to exclude tests from the test run.
 465      * @see #getExcludeListSuccessorQuestion
 466      */
 467     protected abstract Question getExcludeListFirstQuestion();
 468 
 469     /**
 470      * Get the first question to be asked concerning the exclude list
 471      * to be used to exclude tests from the test run.
 472      * @return the first question to be asked concerning the exclude list
 473      * to be used to exclude tests from the test run
 474      * @deprecated Use getExcludeListFirstQuestion().
 475      * @see #getExcludeListFirstQuestion
 476      */
 477     protected Question getExcludeTableFirstQuestion() {
 478         return getExcludeListFirstQuestion();
 479     }
 480 
 481     /**
 482      * Get the next question to be asked after those concerning
 483      * the exclude list to be used to exclude tests from the test run.
 484      * The default value is the result of getKeywordsFirstQuestion,
 485      * @return the next question to be asked after those concerning
 486      * the exclude list to be used to exclude tests from the test run.
 487      * @see #getExcludeListFirstQuestion
 488      */
 489     protected Question getExcludeListSuccessorQuestion() {
 490         return getKeywordsFirstQuestion();
 491     }
 492 
 493     /**
 494      * Get the next question to be asked after those concerning
 495      * the exclude list to be used to exclude tests from the test run.
 496      * @return the next question to be asked after those concerning
 497      * the exclude list to be used to exclude tests from the test run
 498      * @deprecated Use getExcludeListFirstQuestion().
 499      * @see #getExcludeListSuccessorQuestion
 500      */
 501     protected Question getExcludeTableSuccessorQuestion() {
 502         return getExcludeListSuccessorQuestion();
 503     }
 504 
 505     public Keywords getKeywords() {
 506         KeywordsParameters kParams = getKeywordsParameters();
 507         return (kParams == null ? null : kParams.getKeywords());
 508     }
 509 
 510     /**
 511      * Get the first question to be asked concerning the keywords
 512      * that may be used to select tests for the test run.
 513      * @return the first question to be asked concerning the keywords
 514      * that may be used to select tests for the test run.
 515      * @see #getKeywordsSuccessorQuestion
 516      */
 517     protected abstract Question getKeywordsFirstQuestion();
 518 
 519     /**
 520      * Get the next question to be asked after those concerning
 521      * the keywords that may be used to select tests for the test run.
 522      * The default value is the result of getPriorStatusQuestion.
 523      * @return the next question to be asked after those concerning
 524      * the keywords that may be used to select tests for the test run.
 525      * @see #getKeywordsFirstQuestion
 526      */
 527     protected Question getKeywordsSuccessorQuestion() {
 528         return getPriorStatusFirstQuestion();
 529     }
 530 
 531     public boolean[] getPriorStatusValues() {
 532         PriorStatusParameters sParams = getPriorStatusParameters();
 533         return (sParams == null ? null : sParams.getPriorStatusValues());
 534     }
 535 
 536     /**
 537      * Get the first question to be asked concerning whether tests should
 538      * be selected for execution according to their prior execution status.
 539      * @return the first question to be asked concerning whether tests should
 540      * be selected for execution according to their prior execution status.
 541      * @see #getPriorStatusSuccessorQuestion
 542      */
 543     protected abstract Question getPriorStatusFirstQuestion();
 544 
 545     /**
 546      * Get the next question to be asked after those concerning
 547      * whether tests should be selected for execution according to their
 548      * prior execution status.
 549      * The default value is the result of getConcurrencyFirstQuestion
 550      * @return the next question to be asked after those concerning
 551      * whether tests should be selected for execution according to their
 552      * prior execution status.
 553      * @see #getPriorStatusFirstQuestion
 554      */
 555     protected Question getPriorStatusSuccessorQuestion() {
 556         return getConcurrencyFirstQuestion();
 557     }
 558 
 559     public int getConcurrency() {
 560         ConcurrencyParameters cParams = getConcurrencyParameters();
 561         return (cParams == null ? 1 : cParams.getConcurrency());
 562     }
 563 
 564     /**
 565      * Get the first question concerning the number of tests that
 566      * may be run in parallel.
 567      * @return the first question concerning the number of tests that
 568      * may be run in parallel.
 569      * @see #getConcurrencySuccessorQuestion
 570      */
 571     protected abstract Question getConcurrencyFirstQuestion();
 572 
 573     /**
 574      * Get the next question after those concerning the number
 575      * of tests that may be run in parallel.
 576      * The default is the result of getTimeoutFactorFirstQuestion
 577      * @return the next question after those concerning the number
 578      * of tests that may be run in parallel.
 579      * @see #getConcurrencyFirstQuestion
 580      */
 581     protected Question getConcurrencySuccessorQuestion() {
 582         return getTimeoutFactorFirstQuestion();
 583     }
 584 
 585     public float getTimeoutFactor() {
 586         TimeoutFactorParameters tParams = getTimeoutFactorParameters();
 587         return (tParams == null ? 1 : tParams.getTimeoutFactor());
 588     }
 589 
 590     /**
 591      * Get the first question concerning the scale factor to
 592      * be applied to the standard timeout for each test.
 593      * @return the first question concerning the scale factor to
 594      * be applied to the standard timeout for each test.
 595      * @see #getTimeoutFactorSuccessorQuestion
 596      */
 597     protected abstract Question getTimeoutFactorFirstQuestion();
 598 
 599     /**
 600      * Get the next question after those concerning the scale factor to
 601      * be applied to the standard timeout for each test.
 602      * The default is the result of getEpilogFirstQuestion
 603      * @return the next question after those concerning the scale factor to
 604      * be applied to the standard timeout for each test.
 605      * @see #getTimeoutFactorFirstQuestion
 606      */
 607     protected Question getTimeoutFactorSuccessorQuestion() {
 608         return getEpilogFirstQuestion();
 609     }
 610 
 611     /**
 612      * Get the first question of the epilog, which should be asked after
 613      * all the other questions in the configuration interview have been asked.
 614      * The epilog should terminate in the standard way with a FinalQuestion.
 615      * @return the first question of the epilog, which should be asked after
 616      * all the other questions in the configuration interview have been asked.
 617      */
 618     protected abstract Question getEpilogFirstQuestion();
 619 
 620     //----------------------------------------------------------------------------
 621 
 622     /**
 623      * Determine whether all the configuration values are valid, by
 624      * checking if the interview has been completed.
 625      * If so, the result will be true; if not, the result will be false,
 626      * and getErrorMessage will provide details about at least one of the
 627      * invalid values.
 628      * @return true if and only if all the configuration values are valid
 629      * @see #getErrorMessage
 630      * @see #isFinishable
 631      */
 632     public boolean isValid() {
 633         return isFinishable();
 634     }
 635 
 636     /**
 637      * If there is an error in any of the configuration values,
 638      * as indicated by isValid, this method will provide a detail
 639      * message about the first question for which there is a problem.
 640      * @return a detail message about the first question with an invalid answer,
 641      * or null if none.
 642      * @see #isValid
 643      */
 644     public String getErrorMessage() {
 645         Question[] path = getPath();
 646         Question lastQuestion = path[path.length - 1];
 647         if (lastQuestion instanceof FinalQuestion)
 648             return null;
 649         else if (lastQuestion instanceof ErrorQuestion)
 650             return lastQuestion.getText();
 651         else {
 652             String v = lastQuestion.getStringValue();
 653             return i18n.getString("ip.noAnswer",
 654                                   new Object[] { lastQuestion.getSummary(),
 655                                                  lastQuestion.getText(),
 656                                                  lastQuestion.getTag(),
 657                                                  new Integer(v == null ? 0 : 1),
 658                                                  trim(v),
 659                                   } );
 660         }
 661     }
 662 
 663     private String trim(String text) {
 664         return (text == null ? null
 665                 : text.length() < 40 ? text
 666                 : text.substring(0, 37) + "...");
 667     }
 668 
 669 
 670     //----------------------------------------------------------------------------
 671 
 672     /**
 673      * Get a filter which will filter tests according to the result
 674      * of getExcludeList(). If the result of getExcludeList is null
 675      * or an empty exclude list, the result of this method will also be null.
 676      * @return a filter which will filter tests according to the result
 677      * of getExcludeList()
 678      * @deprecated Use getExcludeListFilter().
 679      * @see #getExcludeListFilter
 680      */
 681     public TestFilter getExcludeTableFilter() {
 682         return getExcludeListFilter();
 683     }
 684 
 685     public TestFilter getExcludeListFilter() {
 686         ExcludeList t = getExcludeList();
 687         if (t == null)
 688             cachedExcludeListFilter = null;
 689         else if (cachedExcludeListFilter == null
 690                  || cachedExcludeListFilter.getExcludeList() != t)
 691             cachedExcludeListFilter = new ExcludeListFilter(t);
 692         return cachedExcludeListFilter;
 693     }
 694 
 695     private ExcludeListFilter cachedExcludeListFilter;
 696 
 697     public TestFilter getKeywordsFilter() {
 698         Keywords k = getKeywords();
 699         if (k == null)
 700             cachedKeywordsFilter = null;
 701         else if (cachedKeywordsFilter == null
 702                  || cachedKeywordsFilter.getKeywords() != k)
 703             cachedKeywordsFilter = new KeywordsFilter(k);
 704         return cachedKeywordsFilter;
 705     }
 706 
 707     private KeywordsFilter cachedKeywordsFilter;
 708 
 709     public TestFilter getPriorStatusFilter() {
 710         WorkDirectory wd = getWorkDirectory();
 711         TestResultTable r = (wd == null ? null : wd.getTestResultTable());
 712         boolean[] s = getPriorStatusValues();
 713         if (r == null || s == null)
 714             cachedStatusFilter = null;
 715         else if (cachedStatusFilter == null
 716                  || cachedStatusFilter.getTestResultTable() != r
 717                  || !equal(cachedStatusFilter.getStatusValues(), s))
 718             cachedStatusFilter = new StatusFilter(s, r);
 719         // else
 720         //   cachedStatusFilter is OK
 721 
 722         return cachedStatusFilter;
 723     }
 724 
 725     private StatusFilter cachedStatusFilter;
 726 
 727     public TestFilter getRelevantTestFilter() {
 728         TestSuite ts = getTestSuite();
 729         TestEnvironment env = getEnv();
 730         if (ts == null || env == null)
 731             cachedRelevantTestFilter = null;
 732         else if (cachedRelevantTestFilter == null ||
 733                  ts != cachedRelevantTestFilterTestSuite ||
 734                  env != cachedRelevantTestFilterEnv) {
 735             cachedRelevantTestFilter = ts.createTestFilter(env);
 736         }
 737         return cachedRelevantTestFilter;
 738     }
 739 
 740     private TestFilter cachedRelevantTestFilter;
 741     private TestSuite cachedRelevantTestFilterTestSuite; // do we need this?
 742     private TestEnvironment cachedRelevantTestFilterEnv;
 743 
 744     public synchronized TestFilter[] getFilters() {
 745         Vector<TestFilter> v = new Vector<>();
 746 
 747         TestFilter excludeFilter = getExcludeListFilter();
 748         if (excludeFilter != null) {
 749             v.addElement(excludeFilter);
 750         }
 751 
 752         TestFilter keywordFilter = getKeywordsFilter();
 753         if (keywordFilter != null) {
 754             v.addElement(keywordFilter);
 755         }
 756 
 757         TestFilter statusFilter = getPriorStatusFilter();
 758         if (statusFilter != null) {
 759             v.addElement(statusFilter);
 760         }
 761 
 762         TestFilter testSuiteFilter = null;
 763         try {
 764             testSuiteFilter = getRelevantTestFilter();
 765         }
 766         catch (Exception e) {
 767             testSuiteFilter = null;
 768         }
 769         if (testSuiteFilter != null) {
 770             v.addElement(testSuiteFilter);
 771         }
 772 
 773         if (v.size() == 0) {
 774             return null;
 775         }
 776         else if (equal(v, cachedTestFilters)) {
 777             return cachedTestFilters;
 778         }
 779         else {
 780             TestFilter[] filters = new TestFilter[v.size()];
 781             v.copyInto(filters);
 782             return filters;
 783         }
 784 
 785     }
 786 
 787     private static boolean equal(boolean[] b1, boolean[] b2) {
 788         if (b1 == null || b2 == null)
 789             return (b1 == b2);
 790 
 791         if (b1.length != b2.length)
 792             return false;
 793 
 794         for (int i = 0; i < b1.length; i++) {
 795             if (b1[i] != b2[i])
 796                 return false;
 797         }
 798 
 799         return true;
 800     }
 801 
 802     private static boolean equal(Vector v, TestFilter[] f) {
 803         if (f == null || v.size() != f.length)
 804             return false;
 805         for (int i = 0; i < v.size(); i++) {
 806             if (!v.elementAt(i).equals(f[i]))
 807                 return false;
 808         }
 809         return true;
 810     }
 811 
 812     private TestFilter[] cachedTestFilters;
 813 
 814     //----------------------------------------------------------------------------
 815 
 816     /**
 817      * Determine if the current instance is a template or not.
 818      * @return true if the current instance is a template,
 819      * and false otherwise
 820      */
 821     public boolean isTemplate() {
 822         return isTemplate;
 823     }
 824 
 825     /**
 826      * Set if the current instance is a template or not.
 827      * <b>For internal use only, architects should not use this.</b>
 828      */
 829     public void setTemplate(boolean tm) {
 830         isTemplate = tm;
 831         updatePath();
 832     }
 833 
 834     protected boolean isAutoUpdatableKey(String key) {
 835         return isAutoUpdatableKey(key, null);
 836     }
 837 
 838     protected boolean isAutoUpdatableKey(String key, String subkey) {
 839         return false;
 840     }
 841 
 842     protected boolean isUpdatableKey(String key) {
 843         return true;
 844     }
 845 
 846 
 847     /**
 848      * Return String path to the template file for the current instance.
 849      * If the current instance is a template (isTemplate() returns true),
 850      * the path to itself will be returned.
 851      * @return String path to the template file, or null if the instance is
 852      * not template-based
 853      */
 854     public String getTemplatePath() {
 855         if (isTemplate()) {
 856             File f = getFile();
 857             if (f != null) {
 858                 return f.getPath();
 859             }
 860         }
 861         return templatePath;
 862     }
 863 
 864     /**
 865      * Set the location of a configuration's master template.
 866      * Do not change this value if this instance is a template.
 867      */
 868     public void setTemplatePath(String tu) {
 869         templatePath = tu;
 870     }
 871 
 872 
 873 
 874     /**
 875      * Get the file associated with this interview.
 876      * @return the file associated with this interview.
 877      * @see #setFile
 878      * @see #load
 879      * @see #save
 880      */
 881     public File getFile() {
 882         return currFile;
 883     }
 884 
 885     /**
 886      * Set the file associated with this interview. This file will be used
 887      * by subsequent load and save operations.
 888      * @param f The file to be associated with this interview.
 889      * @see #getFile
 890      * @see #load
 891      * @see #save
 892      */
 893     public void setFile(File f) {
 894         currFile = f;
 895         currFileLoaded = false;
 896         if (f != null) {
 897             currFileLastModified = f.lastModified();
 898         }
 899         else {
 900             // means: unknown; will likely a trigger a reload
 901             currFileLastModified = 0;
 902         }
 903     }
 904 
 905     /**
 906      * Determine if the specified file is an interview file,
 907      * as determined by whether its extension is .jti or not.
 908      * @param f the file to be checked
 909      * @return true if the specified file is an interview file,
 910      * and false otherwise
 911      */
 912     public static boolean isInterviewFile(File f) {
 913         return (f.getName().endsWith(".jti"));
 914     }
 915 
 916     /**
 917      * Create an InterviewParameters as determined by the contents of an
 918      * interview file.
 919      * @param file the file to be read
 920      * @return an InterviewParameters as determined by the contents of an
 921      * interview file.
 922      * @throws IOException is there is a problem reading the file
 923      * @throws Interview.Fault if there is a problem instantiating the
 924      * interview
 925      */
 926     public static InterviewParameters open(File file)
 927         throws IOException, Fault
 928     {
 929         return open(file, (TestSuite) null, (WorkDirectory) null);
 930     }
 931 
 932     /**
 933      * Create an InterviewParameters by populating the interview for a specified
 934      * test suite with responses from a given file.
 935      * @param file the file to be read
 936      * @param testSuite the test suite for which to create the interview
 937      * @return an InterviewParameters as determined by the test suite
 938      * and the contents of an interview file
 939      * @throws IOException is there is a problem reading the file
 940      * @throws Interview.Fault if there is a problem instantiating the
 941      * interview
 942      */
 943     public static InterviewParameters open(File file, TestSuite testSuite)
 944         throws IOException, Fault
 945     {
 946         if (testSuite == null)
 947             throw new NullPointerException();
 948 
 949         return open(file, testSuite, null);
 950     }
 951 
 952     /**
 953      * Create an InterviewParameters by populating the interview for a specified
 954      * work directory with responses from a given file.
 955      * @param file the file to be read
 956      * @param workDir the work directory (implying the test suite) for which
 957      * to create the interview
 958      * @return an InterviewParameters as determined by the work directory
 959      * and the contents of an interview file
 960      * @throws IOException is there is a problem reading the file
 961      * @throws Interview.Fault if there is a problem instantiating the
 962      * interview
 963      */
 964     public static InterviewParameters open(File file, WorkDirectory workDir)
 965         throws IOException, Fault
 966     {
 967         if (workDir == null)
 968             throw new NullPointerException();
 969 
 970         return open(file, workDir.getTestSuite(), workDir);
 971     }
 972 
 973     /**
 974      * @throws WorkDirFault If there is a problem finding the work directory.
 975      * @throws TestSuiteFault If there is a problem finding the test suite.
 976      * @throws JTIFault If there is a problem finding the JTI file.  Not thrown
 977      *                  if the file is corrupt or incompatible though.
 978      * @throws Fault If there is any other problem opening the interview params, such as
 979      *               problems with data in the JTI, incompatibilities between the workdir,
 980      *               test suite or work dir.
 981      */
 982     private static InterviewParameters open(File file, TestSuite testSuite, WorkDirectory workDir)
 983         throws IOException, Fault
 984     {
 985         // note: the additional Fault types were introduced in JT 3.2.1
 986 
 987         // read the .jti data
 988         Map<String, String> data;
 989 
 990         try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
 991             data = Properties.load(in);
 992         }
 993         catch (RuntimeException e) {
 994             // can get IllegalArgumentException if the file is corrupt
 995             throw new JTIFault(i18n, "ip.errorReadingFile", new Object[] { file, e });
 996         }
 997 
 998         // if the test suite has not been given, set it from the .jti data
 999         if (testSuite == null) {
1000             String s = data.get(TESTSUITE);
1001             if (s == null)
1002                 throw new Fault(i18n, "ip.noTestSuiteInFile", file);
1003 
1004             try {
1005                 testSuite = TestSuite.open(new File(s));
1006             }
1007             catch (FileNotFoundException e) {
1008                 throw new TestSuiteFault(i18n, "ip.cantFindTestSuiteInFile",
1009                                 new Object[] { s, file });
1010             }
1011             catch (TestSuite.Fault e) {
1012                 throw new Fault(i18n, "ip.cantOpenTestSuiteInFile",
1013                                 new Object[] { s, file, e.getMessage() } );
1014             }
1015         }
1016 
1017         // if the work directory has not been given,
1018         // set it from the .jti data if given
1019         if (workDir == null) {
1020             String s = data.get(WORKDIR);
1021             if (s != null) {
1022                 try {
1023                     workDir = WorkDirectory.open(new File(s), testSuite);
1024                 }
1025                 catch (FileNotFoundException e) {
1026                     throw new WorkDirFault(i18n, "ip.cantFindWorkDirInFile",
1027                                     new Object[] { s, file } );
1028                 }
1029                 catch (WorkDirectory.Fault e) {
1030                     throw new Fault(i18n, "ip.cantOpenWorkDirInFile",
1031                                     new Object[] { s, file, e.getMessage() } );
1032                 }
1033             }
1034         }
1035 
1036         InterviewParameters parameters;
1037 
1038         // create the parameters object
1039         try {
1040             parameters = testSuite.createInterview();
1041         }
1042         catch (TestSuite.Fault e) {
1043             throw new Fault(i18n, "ip.cantCreateInterviewForTestSuite",
1044                             new Object[] { testSuite.getPath(), e.getMessage() } );
1045         }
1046 
1047         // set the work dir in the parameters object
1048         if (workDir != null)
1049             parameters.setWorkDirectory(workDir);
1050 
1051         // load the .jti data into the parameters object
1052         try {
1053             parameters.load(data, file);
1054         }
1055         catch (InterviewParameters.Fault e) {
1056             throw new Fault(i18n, "ip.cantLoadInterview",
1057                             new Object[] { file, e.getMessage() });
1058         }
1059 
1060         return parameters;
1061     }
1062 
1063     public void clear() {
1064         WorkDirectory wd = getWorkDirectory();
1065         super.clear();
1066 
1067         if (wd != null && TemplateUtilities.getTemplatePath(wd) != null) {
1068             if (wd.getTestSuite() != null) {
1069                 try {
1070                     wd.getTestSuite().loadInterviewFromTemplate(
1071                         TemplateUtilities.getTemplateFile(wd), this);
1072                 } catch (TestSuite.Fault ex) {
1073                     ex.printStackTrace();
1074                 } catch (IOException ex) {
1075                     ex.printStackTrace();
1076                 }
1077             }
1078         }
1079         setEdited(false);
1080         currFile = null;
1081     }
1082 
1083     /**
1084      * Open a a configuration file, based on paths for the configuration file,
1085      * test suite and work directory. Any, but not all, of these paths may be null.
1086      * Any non-null path must specify an appropriate existing file, otherwise
1087      * an exception will be thrown.
1088      * @param testSuitePath the path for the test suite; if not specified,
1089      * the test suite will default from the work directory (if specified) or
1090      * the configuration file.
1091      * @param workDirPath the path for the work directory; if not specified,
1092      * the work directory will the default from the config file (if specified),
1093      * or will be null if no configuration file is given
1094      * @param configFilePath the path for the configuration file; if not specified,
1095      * the result will be a blank interview as created by the test suite.
1096      * @return an InterviewParameters object created from the given arguments
1097      * @throws Interview.Fault if there is any problem creating the
1098      * result
1099      */
1100     public static InterviewParameters open(String testSuitePath, String workDirPath, String configFilePath)
1101         throws InterviewParameters.Fault
1102     {
1103         File ts = (testSuitePath != null && testSuitePath.length() > 0
1104                        ? new File(testSuitePath) : null);
1105         File wd = (workDirPath != null && workDirPath.length() > 0
1106                        ? new File(workDirPath) : null);
1107         File cf = (configFilePath != null && configFilePath.length() > 0
1108                        ? new File(configFilePath) : null);
1109         return open(ts, wd, cf);
1110     }
1111 
1112     /**
1113      * Open a a configuration file, based on paths for the configuration file,
1114      * test suite and work directory. Any, but not all, of these paths may be null.
1115      * Any non-null path must specify an appropriate existing file, otherwise
1116      * an exception will be thrown.
1117      * @param testSuitePath the path for the test suite; if not specified,
1118      * the test suite will default from the work directory (if specified) or
1119      * the configuration file.
1120      * @param workDirPath the path for the work directory; if not specified,
1121      * the work directory will bdefault from the config file (if specified),
1122      * or will be null if no configuration file is given
1123      * @param configFilePath the path for the configuration file; if not specified,
1124      * the result will be a blank interview as created by the test suite.
1125      * @return an InterviewParameters object created from the gievn arguments
1126      * @throws Interview.Fault if there is any problem creating the
1127      * result
1128      */
1129     public static InterviewParameters open(File testSuitePath, File workDirPath, File configFilePath)
1130         throws InterviewParameters.Fault
1131     {
1132 
1133         // open test suite if specified
1134 
1135         TestSuite testSuite;
1136 
1137         if (testSuitePath != null) {
1138             try {
1139                 testSuite = TestSuite.open(testSuitePath);
1140             }
1141             catch (FileNotFoundException e) {
1142                 throw new Fault(i18n, "ip.cantFindTestSuite", testSuitePath);
1143             }
1144             catch (IOException e) {
1145                 throw new Fault(i18n, "ip.cantOpenTestSuite", new Object[] { testSuitePath, e });
1146             }
1147             catch (TestSuite.Fault e) {
1148                 throw new Fault(i18n, "ip.cantOpenTestSuite", new Object[] { testSuitePath, e.getMessage() });
1149             }
1150         }
1151         else
1152             testSuite = null;
1153 
1154         // open work directory if specified, defaulting test suite if appropriate
1155 
1156         WorkDirectory workDir;
1157 
1158         if (workDirPath != null) {
1159             try {
1160                 if (testSuite == null) {
1161                     workDir = WorkDirectory.open(workDirPath);
1162                     testSuite = workDir.getTestSuite();
1163                 }
1164                 else
1165                     workDir = WorkDirectory.open(workDirPath, testSuite);
1166             }
1167             catch (FileNotFoundException e) {
1168                 throw new Fault(i18n, "ip.cantFindWorkDir", workDirPath);
1169             }
1170             catch (IOException e) {
1171                 throw new Fault(i18n, "ip.cantOpenWorkDir", new Object[] { workDirPath, e });
1172             }
1173             catch (WorkDirectory.Fault e) {
1174                 throw new Fault(i18n, "ip.cantOpenWorkDir", new Object[] { workDirPath, e.getMessage() });
1175             }
1176         }
1177         else
1178             workDir = null;
1179 
1180         // open config file if specified, defaulting work dir and test suite if appropriate
1181         // default config from test suite if appropriate
1182 
1183         InterviewParameters config;
1184 
1185         if (configFilePath == null)  {
1186             if (testSuite != null) {
1187                 try {
1188                     config = testSuite.createInterview();
1189                 }
1190                 catch (TestSuite.Fault e) {
1191                     throw new Fault(i18n, "ip.cantCreateInterviewForTestSuite", new Object[] { testSuitePath, e });
1192                 }
1193 
1194                 if (workDir != null) {
1195                     config.setWorkDirectory(workDir);
1196                     FileHistory h = FileHistory.getFileHistory(workDir, "configHistory.jtl");
1197                     File latestConfigFile = h.getLatestEntry();
1198 
1199                     if (latestConfigFile != null) {
1200                         try {
1201                             config.load(latestConfigFile);
1202                         }
1203                         catch (IOException e) {
1204                             // ignore?
1205                         }   // catch
1206                     }
1207                 }   // workdir != null
1208             }
1209             else
1210                 throw new Fault(i18n, "ip.noPaths");
1211         }
1212         else {
1213             try {
1214                 if (workDir == null) {
1215                     if (testSuite == null) {
1216                         config = open(configFilePath);
1217                         testSuite = config.getTestSuite();
1218                     }
1219                     else
1220                         config = open(configFilePath, testSuite);
1221                     workDir = config.getWorkDirectory();
1222                 }
1223                 else
1224                     config = open(configFilePath, workDir);
1225             }
1226             catch (FileNotFoundException e) {
1227                 throw new Fault(i18n, "ip.cantFindConfigFile", configFilePath);
1228             }
1229             catch (IOException e) {
1230                 throw new Fault(i18n, "ip.cantOpenConfigFile", new Object[] { configFilePath, e });
1231             }
1232         }
1233 
1234         // if still here, and had sufficient args, config should be open
1235         // and fully initialized
1236         return config;
1237     }
1238 
1239 
1240 
1241     /**
1242      * Load the interview with the contents of the file associated with
1243      * the interview. If the file does not exist, the interview will be
1244      * cleared.
1245      * @throws IOException is there is a problem reading the file
1246      * @throws Interview.Fault if there is a problem loading the
1247      * interview
1248      * @return true if there was an update from template
1249      */
1250     public boolean load() throws IOException, Fault {
1251         File f = getFile();
1252         if (f != null && f.exists())
1253             return load(f);
1254         else {
1255             clear();
1256             setEdited(false);
1257             return false;
1258         }
1259     }
1260 
1261     /**
1262      * Load the interview with the contents of a specified file,
1263      * which will become the default file associated with the interview.
1264      * @param file the file to be loaded
1265      * @throws FileNotFoundException if the specified file does not exist.
1266      * @throws IOException is there is a problem reading the file
1267      * @throws Interview.Fault if there is a problem loading the
1268      * interview
1269      * @return true if there was an update from template
1270      */
1271     public boolean load(File file) throws FileNotFoundException, IOException, Fault {
1272         try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
1273             Map<String, String> data = Properties.load(in);
1274             return load(data, file);
1275         }
1276     }
1277 
1278     /**
1279      * Load the interview with data that has already been read from a specified file,
1280      * which will become the default file associated with the interview.
1281      * @param data the data to be loaded
1282      * @param file the file from which the data was read
1283      * @throws Interview.Fault if there is a problem loading the interview
1284      * @return true if there was an update from template
1285      */
1286     public boolean load(Map<String, String> data, File file) throws Fault {
1287         load(data);
1288 
1289         // restore template state
1290         String tm = data.get(IS_TEMPLATE);
1291         setTemplate(tm != null && tm.equalsIgnoreCase(TRUE));
1292 
1293         setEdited(false);
1294         currFile = file;
1295         currFileLastModified = file.lastModified();
1296         currFileLoaded = true;
1297         return checkForUpdates();
1298     }
1299     /**
1300      * Returns true if there was update
1301      */
1302     public boolean checkForUpdates() {
1303         InterviewPropagator prop = new InterviewPropagator(this,
1304                                     ignorableProps, ignorablePrefs);
1305         return prop.checkForUpdate();
1306     }
1307 
1308     public void load(Map<String, String> data, boolean checkChecksum) throws Fault {
1309         super.load(data, checkChecksum);
1310 
1311         String me = data.get(MARKERS_ENABLED);
1312         setMarkersEnabled(me != null && me.equalsIgnoreCase(TRUE));
1313 
1314         String mf = data.get(MARKERS_FILTER);
1315         setMarkersFilterEnabled(mf != null && mf.equalsIgnoreCase(TRUE));
1316 
1317         String tm = data.get(IS_TEMPLATE);
1318         setTemplate(tm != null && tm.equalsIgnoreCase(TRUE));
1319 
1320         String tu = null;
1321         //if (isTemplate()) {
1322             tu = data.get(TEMPLATE_PATH);
1323         //} else {
1324             //tu = (String) data.get(TEMPLATE_PREF + TEMPLATE_PATH);
1325         //}
1326         setTemplatePath(tu);
1327     }
1328 
1329     /**
1330      * Load the interview as best as possible with the data in another
1331      * Parameters object. If any of the various sub-objects as returned by
1332      * get<i>XXX</i>Parameters are not recognized, they will be ignored.
1333      * @param other The Parameters object to be copied.
1334      */
1335     public void load(Parameters other) {
1336         loadTestsParameters(other.getTestsParameters());
1337         loadExcludeListParameters(other.getExcludeListParameters());
1338         loadKeywordsParameters(other.getKeywordsParameters());
1339         loadPriorStatusParameters(other.getPriorStatusParameters());
1340         loadEnvParameters(other.getEnvParameters());
1341         loadConcurrencyParameters(other.getConcurrencyParameters());
1342         loadTimeoutFactorParameters(other.getTimeoutFactorParameters());
1343     }
1344 
1345     private void loadTestsParameters(TestsParameters other) {
1346         TestsParameters tp = getTestsParameters();
1347         if (!(tp instanceof MutableTestsParameters))
1348             return;
1349 
1350         MutableTestsParameters mtp = (MutableTestsParameters) tp;
1351 
1352         if (other instanceof MutableTestsParameters) {
1353             MutableTestsParameters mop = (MutableTestsParameters) other;
1354             mtp.setTestsMode(mop.getTestsMode());
1355             mtp.setSpecifiedTests(mop.getSpecifiedTests());
1356         }
1357         else {
1358             String[] tests = other.getTests();
1359             if (tests == null) {
1360                 mtp.setTestsMode(MutableTestsParameters.ALL_TESTS);
1361                 mtp.setSpecifiedTests(null);
1362             }
1363             else {
1364                 mtp.setTestsMode(MutableTestsParameters.SPECIFIED_TESTS);
1365                 mtp.setSpecifiedTests(tests);
1366             }
1367         }
1368     }
1369 
1370     private void loadExcludeListParameters(ExcludeListParameters other) {
1371         ExcludeListParameters tp = getExcludeListParameters();
1372         if (!(tp instanceof MutableExcludeListParameters))
1373             return;
1374 
1375         MutableExcludeListParameters mtp = (MutableExcludeListParameters) tp;
1376 
1377         if (other instanceof MutableExcludeListParameters) {
1378             MutableExcludeListParameters mop = (MutableExcludeListParameters) other;
1379             mtp.setExcludeMode(mop.getExcludeMode());
1380             mtp.setCustomExcludeFiles(mop.getCustomExcludeFiles());
1381             mtp.setLatestExcludeAutoCheckEnabled(mop.isLatestExcludeAutoCheckEnabled());
1382             mtp.setLatestExcludeAutoCheckMode(mop.getLatestExcludeAutoCheckMode());
1383             mtp.setLatestExcludeAutoCheckInterval(mop.getLatestExcludeAutoCheckInterval());
1384         }
1385         else {
1386             mtp.setExcludeMode(MutableExcludeListParameters.CUSTOM_EXCLUDE_LIST);
1387             mtp.setCustomExcludeFiles(null);
1388             mtp.setLatestExcludeAutoCheckEnabled(false);
1389             mtp.setLatestExcludeAutoCheckMode(MutableExcludeListParameters.CHECK_EVERY_X_DAYS);
1390             mtp.setLatestExcludeAutoCheckInterval(0);
1391         }
1392     }
1393 
1394     private void loadKeywordsParameters(KeywordsParameters other) {
1395         KeywordsParameters tp = getKeywordsParameters();
1396         if (!(tp instanceof MutableKeywordsParameters))
1397             return;
1398 
1399         MutableKeywordsParameters mtp = (MutableKeywordsParameters) tp;
1400 
1401         if (other instanceof MutableKeywordsParameters) {
1402             MutableKeywordsParameters mop = (MutableKeywordsParameters) other;
1403             mtp.setKeywordsMode(mop.getKeywordsMode());
1404             mtp.setMatchKeywords(mop.getMatchKeywordsMode(), mop.getMatchKeywordsValue());
1405         }
1406         else {
1407             Keywords k = other.getKeywords();
1408             if (k == null) {
1409                 mtp.setKeywordsMode(MutableKeywordsParameters.NO_KEYWORDS);
1410                 mtp.setMatchKeywords(MutableKeywordsParameters.EXPR, "");
1411             }
1412             else {
1413                 mtp.setKeywordsMode(MutableKeywordsParameters.MATCH_KEYWORDS);
1414                 mtp.setMatchKeywords(MutableKeywordsParameters.EXPR, k.toString());
1415             }
1416 
1417         }
1418     }
1419 
1420     private void loadPriorStatusParameters(PriorStatusParameters other) {
1421         PriorStatusParameters tp = getPriorStatusParameters();
1422         if (!(tp instanceof MutablePriorStatusParameters))
1423             return;
1424 
1425         MutablePriorStatusParameters mtp = (MutablePriorStatusParameters) tp;
1426 
1427         if (other instanceof MutablePriorStatusParameters) {
1428             MutablePriorStatusParameters mop = (MutablePriorStatusParameters) other;
1429             mtp.setPriorStatusMode(mop.getPriorStatusMode());
1430             mtp.setMatchPriorStatusValues(mop.getMatchPriorStatusValues());
1431         }
1432         else {
1433             boolean[] b = other.getPriorStatusValues();
1434             if (b == null) {
1435                 mtp.setPriorStatusMode(MutablePriorStatusParameters.NO_PRIOR_STATUS);
1436                 mtp.setMatchPriorStatusValues(new boolean[Status.NUM_STATES]);
1437             }
1438             else {
1439                 mtp.setPriorStatusMode(MutablePriorStatusParameters.MATCH_PRIOR_STATUS);
1440                 mtp.setMatchPriorStatusValues(b);
1441             }
1442         }
1443     }
1444 
1445     private void loadEnvParameters(EnvParameters other) {
1446         EnvParameters tp = getEnvParameters();
1447         if (!(tp instanceof LegacyEnvParameters))
1448             return;
1449 
1450         LegacyEnvParameters ltp = (LegacyEnvParameters) tp;
1451 
1452         if (other instanceof LegacyEnvParameters) {
1453             LegacyEnvParameters lop = (LegacyEnvParameters) other;
1454             ltp.setEnvFiles(lop.getEnvFiles());
1455             ltp.setEnvName(lop.getEnvName());
1456         }
1457     }
1458 
1459     private void loadConcurrencyParameters(ConcurrencyParameters other) {
1460         ConcurrencyParameters tp = getConcurrencyParameters();
1461         if (!(tp instanceof MutableConcurrencyParameters))
1462             return;
1463 
1464         MutableConcurrencyParameters mtp = (MutableConcurrencyParameters) tp;
1465         mtp.setConcurrency(other.getConcurrency());
1466     }
1467 
1468     private void loadTimeoutFactorParameters(TimeoutFactorParameters other) {
1469         TimeoutFactorParameters tp = getTimeoutFactorParameters();
1470         if (!(tp instanceof MutableTimeoutFactorParameters))
1471             return;
1472 
1473         MutableTimeoutFactorParameters mtp = (MutableTimeoutFactorParameters) tp;
1474         mtp.setTimeoutFactor(other.getTimeoutFactor());
1475     }
1476 
1477     /**
1478      * Save the current set of answers for the interview in the standard
1479      * file associated with the interview.
1480      * @throws IOException is there is a problem writing the file
1481      * @throws Interview.Fault if there is a problem preparing the
1482      * interview to be written
1483      * @see #getFile
1484      */
1485     public void save() throws IOException, Fault {
1486         File f = getFile();
1487         if (f == null)
1488             throw new IllegalStateException();
1489         save(f);
1490     }
1491 
1492 
1493     /**
1494      * Save the current state of the interview in a specified file,
1495      * and make that file the new file associated with the interview.
1496      * @param file the file in which to save the state of the interview
1497      * @throws IOException is there is a problem writing the file
1498      * @throws Interview.Fault if there is a problem preparing the
1499      * interview to be written
1500      * @see #getFile
1501      */
1502     public void save(File file) throws IOException, Fault {
1503         save(file, false);
1504     }
1505 
1506 
1507 
1508     /**
1509      * Save the current state of the interview in a specified file,
1510      * and make that file the new file associated with the interview.
1511      * @param file the file in which to save the state of the interview
1512      * @param isTemplate
1513      * @throws IOException is there is a problem writing the file
1514      * @throws Interview.Fault if there is a problem preparing the
1515      * interview to be written
1516      * @see #getFile
1517      */
1518     public void save(File file, boolean isTemplate) throws IOException, Fault {
1519         saveAs(file, true, true, isTemplate);
1520 
1521         setEdited(false);
1522         currFile = file;
1523         currFileLastModified = file.lastModified();
1524         currFileLoaded = true;
1525     }
1526 
1527 
1528     /**
1529      * Save the current state of the interview in a specified file,
1530      * including the paths for the test suite and work directory.
1531      * @param file the file in which to save the state of the interview
1532      * @throws IOException is there is a problem writing the file
1533      * @throws Interview.Fault if there is a problem preparing the
1534      * interview to be written
1535      */
1536     public void saveAs(File file)
1537         throws IOException, Fault
1538     {
1539         saveAs(file, true, true);
1540     }
1541 
1542 
1543     /**
1544      * Save the current state of the interview in a specified file.
1545      * If the test suite path is not saved, the file can only be used
1546      * as a configuration template.
1547      * @param file the file in which to save the state of the interview
1548      * @param saveTestSuite if true, the test suite path will be saved
1549      * in the file.
1550      * @param saveWorkDir if true, the work directory path will be saved
1551      * in the file.
1552      * @param isTemplate True, the interview will be saved as template.
1553      * @throws IOException is there is a problem writing the file
1554      * @throws Interview.Fault if there is a problem preparing the
1555      * interview to be written
1556      */
1557     public void saveAs(File file, boolean saveTestSuite, boolean saveWorkDir, boolean isTemplate)
1558         throws IOException, Fault
1559     {
1560         SortedMap<String, String> data = new TreeMap<>();
1561         setTemplate(isTemplate);        // dubious, why do we need to do this?
1562 
1563         if (saveTestSuite) {
1564             TestSuite ts = getTestSuite();
1565             if (ts != null)
1566                 data.put(TESTSUITE, ts.getPath());
1567         }
1568 
1569         if (saveWorkDir) {
1570             WorkDirectory wd = getWorkDirectory();
1571             if (wd != null)
1572                 data.put(WORKDIR, wd.getPath());
1573         }
1574 
1575         save(data);
1576 
1577         if (this.isTemplate == true ) {
1578             TemplateManager tm = this.templateManager;
1579             if (tm != null && !tm.canSaveTemplate(file)) {
1580                 throw new Interview.Fault(i18n, "ip.badTmplPath");
1581             }
1582         }
1583 
1584         OutputStream out;
1585         if (backupPolicy == null)
1586             out = new BufferedOutputStream(new FileOutputStream(file));
1587         else
1588             out = backupPolicy.backupAndOpenStream(file);
1589 
1590         try {
1591             Properties.store(data, out, "JT Harness Configuration Interview");
1592         }
1593         finally {
1594             out.close();
1595         }
1596     }
1597 
1598 
1599 
1600     /**
1601      * Save the current state of the interview in a specified file.
1602      * If the test suite path is not saved, the file can only be used
1603      * as a configuration template.
1604      * @param file the file in which to save the state of the interview
1605      * @param saveTestSuite if true, the test suite path will be saved
1606      * in the file.
1607      * @param saveWorkDir if true, the work directory path will be saved
1608      * in the file.
1609      * @throws IOException is there is a problem writing the file
1610      * @throws Interview.Fault if there is a problem preparing the
1611      * interview to be written
1612      */
1613     public void saveAs(File file, boolean saveTestSuite, boolean saveWorkDir)
1614         throws IOException, Fault
1615     {
1616         saveAs(file, saveTestSuite, saveWorkDir, false);
1617     }
1618 
1619     public void save(Map<String, String> data) {
1620         if (markersEnabled)
1621             data.put(MARKERS_ENABLED, TRUE);
1622 
1623         if (markersFilterEnabled)
1624             data.put(MARKERS_FILTER, TRUE);
1625 
1626         if (isTemplate()) {
1627             data.put(IS_TEMPLATE, TRUE);
1628 
1629             storeTemplateProperties(new HashMap<String, String>());
1630         }
1631         else {
1632             WorkDirectory wd = getWorkDirectory();
1633             if (wd != null && TemplateUtilities.getTemplatePath(wd) != null)
1634                 data.put(TEMPLATE_PATH, TemplateUtilities.getTemplatePath(wd));
1635         }
1636 
1637         String name = getName();
1638         if (name != null)
1639             data.put(NAME, name);
1640 
1641         String desc = getDescription();
1642         if (desc != null)
1643             data.put(DESC, desc);
1644 
1645 
1646         super.save(data);
1647     }
1648 
1649     /**
1650      * Get the backup policy to be used when saving configuration files.
1651      * @return the backup policy object to be used when saving configuration files
1652      * @see #setBackupPolicy
1653      */
1654     public BackupPolicy getBackupPolicy() {
1655         return backupPolicy;
1656     }
1657 
1658     /**
1659      * Set the backup policy to be used when saving configuration files.
1660      * @param backupPolicy the backup policy object to be used when saving configuration files
1661      * @see #getBackupPolicy
1662      */
1663     public void setBackupPolicy(BackupPolicy backupPolicy) {
1664         this.backupPolicy = backupPolicy;
1665     }
1666 
1667     /**
1668      * Check if the current file has been loaded into this interview,
1669      * or if the interview has been saved in it.
1670      * @return true if the file associated with the interview was set as a
1671      * side effect of load or save, or false if the file was just set by
1672      * setFile.
1673      */
1674     public boolean isFileLoaded() {
1675         return currFileLoaded;
1676     }
1677 
1678     /**
1679      * Determine if the file associated with this interview has been modified
1680      * on disk after the last call of load or save.
1681      * @return true if the file on disk has been modified after it was last
1682      * used by load or save.
1683      * @see #load()
1684      * @see #save()
1685      */
1686     public boolean isFileNewer() {
1687         File f = getFile();
1688         return (f != null && f.exists() && ((currFileLastModified == 0)
1689                                || (f.lastModified() > currFileLastModified)));
1690     }
1691 
1692     //----------------------------------------------------------------------------
1693 
1694     /**
1695      * Check whether or not markers should be enabled.
1696      * @return whether or not markers should be enabled
1697      * @see #setMarkersEnabled
1698      */
1699     public boolean getMarkersEnabled() {
1700         return markersEnabled;
1701     }
1702 
1703     /**
1704      * Specify whether or not markers should be enabled.
1705      * @param on whether or not markers should be enabled
1706      * @see #getMarkersEnabled
1707      */
1708     public void setMarkersEnabled(boolean on) {
1709         if (on != markersEnabled) {
1710             markersEnabled = on;
1711             setEdited(true);
1712         }
1713     }
1714 
1715     /**
1716      * Check whether or not the history list should be filtered to
1717      * just show questions which have been marked.
1718      * @return whether or not the  history list should be filtered to
1719      * just show questions which have been marked
1720      * @see #setMarkersFilterEnabled
1721      */
1722     public boolean getMarkersFilterEnabled() {
1723         return markersFilterEnabled;
1724     }
1725 
1726     /**
1727      * Specify whether or not the history list should be filtered to
1728      * just show questions which have been marked.
1729      * @param on whether or not the  history list should be filtered to
1730      * just show questions which have been marked
1731      * @see #getMarkersFilterEnabled
1732      */
1733     public void setMarkersFilterEnabled(boolean on) {
1734         if (on != markersFilterEnabled) {
1735             markersFilterEnabled = on;
1736             setEdited(true);
1737         }
1738     }
1739 
1740     //----------------------------------------------------------------------------
1741     private final String [] ignorableProps = new String [] {
1742         INTERVIEW,
1743         LOCALE, TESTSUITE, WORKDIR, MARKERS,
1744         IS_TEMPLATE, TEMPLATE_PATH, QUESTION};
1745     private final String [] ignorablePrefs = new String [] { MARKERS_PREF, EXTERNAL_PREF, TEMPLATE_PREF};
1746 
1747     private BackupPolicy backupPolicy;
1748     private boolean markersEnabled;
1749     private boolean markersFilterEnabled;
1750 
1751     private File currFile;
1752     private boolean isTemplate;
1753     private String templatePath;
1754     private long currFileLastModified;
1755     private boolean currFileLoaded;
1756     protected File[] kflFiles;
1757 
1758     private CustomPropagationController pc =  new CustomPropagationController();
1759 
1760     static final String TESTSUITE = "TESTSUITE";
1761     static final String WORKDIR = "WORKDIR";
1762     static final String NAME = "NAME";
1763     static final String DESC = "DESCRIPTION";
1764     static final String MARKERS_ENABLED = "MARKERS.enabled";
1765     static final String MARKERS_FILTER = "MARKERS.filter";
1766     static final String IS_TEMPLATE = "IS_TEMPLATE";
1767     static final String TEMPLATE_PATH = "TEMPLATE_PATH";
1768     static final String TRUE = "true";
1769 
1770     private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(InterviewParameters.class);
1771 }