1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2002, 2013, 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.*;
  30 import java.net.URL;
  31 import java.net.URLClassLoader;
  32 import java.nio.charset.StandardCharsets;
  33 import java.text.MessageFormat;
  34 import java.util.Properties;
  35 import java.util.ResourceBundle;
  36 
  37 import com.sun.interview.Interview;
  38 import com.sun.interview.Question;
  39 import com.sun.interview.WizPrint;
  40 import com.sun.javatest.util.BackupPolicy;
  41 import com.sun.javatest.util.I18NResourceBundle;
  42 
  43 /**
  44  * This class provides a utility for command-line editing of configuration (.jti) files.
  45  * It is intended to be invoked from the command line, as in: <pre>
  46  * java com.sun.javatest.EditJIT options...
  47  * </pre>
  48  * For details of the options, use the <code>-help</code> option.
  49  */
  50 public class EditJTI
  51 {
  52     /**
  53      * This exception is used to indicate a problem with the command line arguments.
  54      */
  55     public static class BadArgs extends Exception
  56     {
  57         /**
  58          * Create a BadArgs exception.
  59          * @param i18n A resource bundle in which to find the detail message.
  60          * @param s The key for the detail message.
  61          */
  62         BadArgs(ResourceBundle i18n, String s) {
  63             super(i18n.getString(s));
  64         }
  65 
  66         /**
  67          * Create a BadArgs exception.
  68          * @param i18n A resource bundle in which to find the detail message.
  69          * @param s The key for the detail message.
  70          * @param o An argument to be formatted with the detail message by
  71          * {@link java.text.MessageFormat#format}
  72          */     BadArgs(ResourceBundle i18n, String s, Object o) {
  73             super(MessageFormat.format(i18n.getString(s), new Object[] {o}));
  74         }
  75 
  76 
  77         /**
  78          * Create a BadArgs exception.
  79          * @param i18n A resource bundle in which to find the detail message.
  80          * @param s The key for the detail message.
  81          * @param o An array of arguments to be formatted with the detail message by
  82          * {@link java.text.MessageFormat#format}
  83          */
  84         BadArgs(ResourceBundle i18n, String s, Object[] o) {
  85             super(MessageFormat.format(i18n.getString(s), o));
  86         }
  87     }
  88 
  89     /**
  90      * This exception is used to report problems that arise when using this API.
  91      */
  92     public static class Fault extends Exception
  93     {
  94         Fault(I18NResourceBundle i18n, String s) {
  95             super(i18n.getString(s));
  96         }
  97 
  98         Fault(I18NResourceBundle i18n, String s, Object o) {
  99             super(i18n.getString(s, o));
 100         }
 101 
 102         Fault(I18NResourceBundle i18n, String s, Object[] o) {
 103             super(i18n.getString(s, o));
 104         }
 105     }
 106 
 107     /**
 108      * Command line entry point. Run with <code>-help</code> to get
 109      * brief command line help.  Warning: this method uses System.exit
 110      * and so does not return if called directly.
 111      * @param args Comamnd line arguments.
 112      */
 113     public static void main(String[] args) {
 114         try {
 115             EditJTI e = new EditJTI();
 116             boolean ok = e.run(args);
 117             System.exit(ok ? 0 : 1);
 118         }
 119         catch (BadArgs e) {
 120             System.err.println(e.getMessage());
 121             usage(System.err);
 122             System.exit(2);
 123         }
 124         catch (Fault e) {
 125             System.err.println(e.getMessage());
 126             System.exit(3);
 127         }
 128     }
 129 
 130     /**
 131      * Print out brief command line help.
 132      * @param out the stream to which to write the command line help.
 133      */
 134     public static void usage(PrintStream out) {
 135         String prog = System.getProperty("program", "java " + EditJTI.class.getName());
 136         out.println(i18n.getString("editJTI.usage.title"));
 137         out.print("  ");
 138         out.print(prog);
 139         out.println(i18n.getString("editJTI.usage.summary"));
 140 
 141         out.println(i18n.getString("editJTI.usage.options"));
 142         out.println(i18n.getString("editJTI.usage.help1"));
 143         out.println(i18n.getString("editJTI.usage.help2"));
 144         out.println(i18n.getString("editJTI.usage.help3"));
 145         out.println(i18n.getString("editJTI.usage.classpath1"));
 146         out.println(i18n.getString("editJTI.usage.classpath2"));
 147         out.println(i18n.getString("editJTI.usage.log1"));
 148         out.println(i18n.getString("editJTI.usage.log2"));
 149         out.println(i18n.getString("editJTI.usage.outfile1"));
 150         out.println(i18n.getString("editJTI.usage.outfile2"));
 151         out.println(i18n.getString("editJTI.usage.path1"));
 152         out.println(i18n.getString("editJTI.usage.path2"));
 153         out.println(i18n.getString("editJTI.usage.preview1"));
 154         out.println(i18n.getString("editJTI.usage.preview2"));
 155         out.println(i18n.getString("editJTI.usage.ts1"));
 156         out.println(i18n.getString("editJTI.usage.ts2"));
 157         out.println(i18n.getString("editJTI.usage.verbose1"));
 158         out.println(i18n.getString("editJTI.usage.verbose2"));
 159         out.println("");
 160         out.println(i18n.getString("editJTI.usage.edit"));
 161         out.println(i18n.getString("editJTI.usage.set"));
 162         out.println(i18n.getString("editJTI.usage.search"));
 163         out.println("");
 164     }
 165 
 166     /**
 167      * Run the utility, without exiting.  Any messages are written to
 168      * the standard output stream.
 169      * @param args command line args
 170      * @return true if the resulting configuration is valid (complete),
 171      * and false otherwise.
 172      * @throws EditJTI.BadArgs if there is an error analysing the args
 173      * @throws EditJTI.Fault if there is an error executing the args
 174      */
 175     public boolean run(String[] args) throws BadArgs, Fault {
 176         PrintWriter out = new PrintWriter(System.out);
 177         try {
 178             return run(args, out);
 179         }
 180         finally {
 181             out.flush();
 182         }
 183     }
 184 
 185 
 186     /**
 187      * Run the utility, without exiting, writing any messages to a specified stream.
 188      * @param args command line args
 189      * @param out the stream to which to write any messages
 190      * @return true if the resulting configuration is valid (complete),
 191      * and false otherwise.
 192      * @throws EditJTI.BadArgs if there is an error analysing the args
 193      * @throws EditJTI.Fault if there is an error executing the args
 194      */
 195     public boolean run(String[] args, PrintWriter out) throws BadArgs, Fault {
 196         File inFile = null;
 197         File outFile = null;
 198         File logFile = null;
 199         File classPath = null;
 200         File testSuitePath = null;
 201         File workDirPath = null;
 202         String[] editCmds = null;
 203         boolean helpFlag = false;
 204         boolean previewFlag = false;
 205         boolean showPathFlag = false;
 206         boolean verboseFlag = false;
 207 
 208         for (int i = 0; i < args.length; i++) {
 209             if ((args[i].equals("-o") || args[i].equals("-out"))
 210                 && i + 1 < args.length) {
 211                 checkUnset(outFile, args[i]);
 212                 outFile = new File(args[++i]);
 213             }
 214             else if ((args[i].equals("-i") || args[i].equals("-in"))
 215                 && i + 1 < args.length) {
 216                 checkUnset(inFile, args[i]);
 217                 inFile = new File(args[++i]);
 218             }
 219             else if ((args[i].equals("-l") || args[i].equals("-log"))
 220                      && i + 1 < args.length) {
 221                 checkUnset(logFile, args[i]);
 222                 logFile = new File(args[++i]);
 223             }
 224             else if (args[i].equals("-n") || args[i].equals("-preview"))
 225                 previewFlag = true;
 226             else if (args[i].equals("-p") || args[i].equals("-path"))
 227                 showPathFlag = true;
 228             else if (args[i].equals("-v") || args[i].equals("-verbose")  )
 229                 verboseFlag = true;
 230             else if ((args[i].equals("-cp") || args[i].equals("-classpath")) && i + 1 < args.length) {
 231                 checkUnset(classPath, args[i]);
 232                 classPath = new File(args[++i]);
 233             }
 234             else if ((args[i].equals("-ts") || args[i].equals("-testsuite")) && i + 1 < args.length) {
 235                 checkUnset(testSuitePath, args[i]);
 236                 testSuitePath = new File(args[++i]);
 237             }
 238             else if ((args[i].equals("-wd") || args[i].equals("-workdir")) && i + 1 < args.length) {
 239                 checkUnset(testSuitePath, args[i]);
 240                 workDirPath = new File(args[++i]);
 241             }
 242             else if (args[i].equals("-help") || args[i].equals("-usage") || args[i].equals("/?") )
 243                 helpFlag = true;
 244             else if (args[i].startsWith("-"))
 245                 throw new BadArgs(i18n, "editJTI.badOption", args[i]);
 246             else if (i <= args.length - 1) {
 247                 if (inFile == null) {
 248                     editCmds = new String[args.length - 1 - i];
 249                     System.arraycopy(args, i, editCmds, 0, editCmds.length);
 250                     inFile = new File(args[args.length - 1]);
 251                 }
 252                 else {
 253                     editCmds = new String[args.length - i];
 254                     System.arraycopy(args, i, editCmds, 0, editCmds.length);
 255                 }
 256                 i = args.length - 1;
 257             }
 258             else
 259                 throw new BadArgs(i18n, "editJTI.badOption", args[i]);
 260         }
 261 
 262         if (args.length == 0 || helpFlag) {
 263             usage(System.out);
 264             if (inFile == null)
 265                 return true;
 266         }
 267 
 268         if (classPath != null && testSuitePath != null)
 269             throw new BadArgs(i18n, "editJTI.cantHaveClassPathAndTestSuite");
 270 
 271         if (inFile == null)
 272             throw new BadArgs(i18n, "editJTI.noInterview");
 273 
 274         // if (editCmds.length == 0 && outFile == null && logFile == null && !showPathFlag)
 275         //     throw new BadArgs(...no.actions....);
 276 
 277         verbose = verboseFlag;
 278         this.out = out;
 279 
 280         try {
 281             /* the following looks nice and simple, but breaks compatibility
 282                with 3.1.4, because InterviewParameters.open will try and open
 283                the wd in the .jti file if not given explicitly -- and previously,
 284                this was not required/done.  So, only use the simple code if wd
 285                is set, and use the old 3.1.4 code if wd is not set.
 286             */
 287             /* See comment above
 288             if (workDirPath != null || testSuitePath != null) {
 289                 interview = InterviewParameters.open(testSuitePath, workDirPath, inFile);
 290             }
 291             */
 292             if (workDirPath != null)
 293                 interview = InterviewParameters.open(testSuitePath, workDirPath, inFile);
 294             else if (testSuitePath != null) {
 295                 // only open the test suite, not the work dir
 296                 TestSuite ts;
 297                 try {
 298                     ts = TestSuite.open(testSuitePath);
 299                 }
 300                 catch (FileNotFoundException e) {
 301                     throw new Fault(i18n, "editJTI.cantFindTestSuite", testSuitePath);
 302                 }
 303                 catch (TestSuite.NotTestSuiteFault e) {
 304                     throw new Fault(i18n, "editJTI.notATestSuite", testSuitePath);
 305                 }
 306                 catch (TestSuite.Fault e) {
 307                     throw new Fault(i18n, "editJTI.cantOpenTestSuite",
 308                                     new Object[] { testSuitePath, e });
 309                 }
 310                 load(inFile, ts);
 311             }
 312             // End of patches for 3.1.4 compatibility
 313             else if (classPath != null) {
 314                 URLClassLoader loader = new URLClassLoader(new URL[] { classPath.toURL() });
 315                 load(inFile, loader);
 316             }
 317             else
 318                 load(inFile);
 319 
 320         }
 321         catch (Interview.Fault e) {
 322             throw new Fault(i18n, "editJTI.cantOpenFile",
 323                             new Object[] { inFile.getPath(), e.getMessage() });
 324         }
 325         catch (FileNotFoundException e) {
 326             throw new Fault(i18n, "editJTI.cantFindFile", inFile.getPath());
 327         }
 328         catch (IOException e) {
 329             throw new Fault(i18n, "editJTI.cantOpenFile",
 330                             new Object[] { inFile.getPath(), e });
 331         }
 332         catch (IllegalStateException e) {
 333             // only occurs if keywords are being used in the config, and the
 334             // test suite is not available.  user needs to specify -wd or -ts
 335             if (verbose)
 336                 e.printStackTrace();
 337 
 338             throw new Fault(i18n, "editJTI.badState", e.getMessage());
 339         }
 340 
 341         if (NUM_BACKUPS > 0)
 342             interview.setBackupPolicy(BackupPolicy.simpleBackups(NUM_BACKUPS));
 343 
 344         if (editCmds != null)
 345             edit(editCmds);
 346 
 347 
 348         if (showPathFlag)
 349             showPath();
 350 
 351         try {
 352             if (logFile != null) {
 353                 if (previewFlag) {
 354                     String msg = i18n.getString("editJTI.wouldWriteLog", logFile);
 355                     out.println(msg);
 356                 }
 357                 else
 358                     writeLog(logFile);
 359             }
 360         }
 361         catch (IOException e) {
 362             throw new Fault(i18n, "editJTI.cantWriteLog",
 363                             new Object[] { logFile.getPath(), e });
 364         }
 365 
 366 
 367         try {
 368             if (previewFlag) {
 369                 String msg;
 370                 if (interview.isEdited())
 371                     msg = i18n.getString("editJTI.wouldSaveEdited",
 372                                 (outFile != null ? outFile : inFile));
 373                 else if (outFile != null)
 374                     msg = i18n.getString("editJTI.wouldSaveNotEdited", outFile);
 375                 else
 376                     msg = i18n.getString("editJTI.wouldNotSave");
 377                 out.println(msg);
 378             }
 379             else {
 380                 if (outFile != null)
 381                     save(outFile);
 382                 else if (interview.isEdited())
 383                     save(inFile);
 384             }
 385         }
 386         catch (Interview.Fault e) {
 387             throw new Fault(i18n, "editJTI.cantOpenFile",
 388                             new Object[] {
 389                                 (outFile == null || outFile.getPath() == null ?
 390                                  "??": outFile.getPath()), e });
 391         }
 392         catch (IOException e) {
 393             File f = (outFile == null ? interview.getFile() : outFile);
 394             throw new Fault(i18n, "editJTI.cantSaveFile",
 395                             new Object[] { f.getPath(), e });
 396         }
 397 
 398         return (interview.isFinishable());
 399     }
 400 
 401     /**
 402      * Load a configuration file to be edited.
 403      * @param inFile the file to be loaded
 404      * @throws IOException if there is a problem reading the file
 405      * @throws Interview.Fault if there is a problem loading the interview data from the file
 406      */
 407     public void load(File inFile) throws IOException, Interview.Fault {
 408         // this opens the interview via the work directory and test suite;
 409         // the test suite implicitly knows its classpath via the .jtt file
 410         interview = InterviewParameters.open(inFile);
 411         interview.setEdited(false);
 412     }
 413 
 414     /**
 415      * Load a configuration file to be edited.
 416      * @param inFile the file to be loaded
 417      * @param ts the test suite for which the interview is to be loaded
 418      * @throws IOException if there is a problem reading the file
 419      * @throws Interview.Fault if there is a problem loading the interview data from the file
 420      * @throws EditJTI.Fault if there is a problem creating the interview for the testsuite
 421      */
 422     public void load(File inFile, TestSuite ts)
 423         throws IOException, Interview.Fault, Fault
 424     {
 425         // this opens the interview via the work directory and test suite;
 426         // the test suite implicitly knows its classpath via the .jtt file
 427         try {
 428             interview = ts.createInterview();
 429         }
 430         catch (TestSuite.Fault e) {
 431             throw new Fault(i18n, "editJTI.cantCreateInterviewForTestSuite",
 432                             new Object[] { ts.getPath(), e.getMessage() });
 433         }
 434         interview.load(inFile);
 435         interview.setEdited(false);
 436     }
 437 
 438     /**
 439      * Load a configuration file to be edited, using a specified class loader
 440      * to load the interview class.
 441      * @param inFile the file to be loaded
 442      * @param loader the class loader to be used to load the interview class
 443      * @throws IOException if there is a problem reading the file
 444      * @throws Interview.Fault if there is a problem loading the interview data from the file
 445      * @throws EditJTI.Fault if there is a problem creating the interview for the testsuite
 446      */
 447     public void load(File inFile, URLClassLoader loader)
 448         throws IOException, Interview.Fault, Fault
 449     {
 450         InputStream in = new BufferedInputStream(new FileInputStream(inFile));
 451         Properties p = new Properties();
 452         p.load(in);
 453         in.close();
 454 
 455         String interviewClassName = (String) (p.get("INTERVIEW"));
 456         try {
 457             Class interviewClass = loader.loadClass(interviewClassName);
 458 
 459             interview = (InterviewParameters)(interviewClass.newInstance());
 460         }
 461         catch (ClassCastException e) {
 462             throw new Fault(i18n, "editJTI.invalidInterview", inFile);
 463         }
 464         catch (ClassNotFoundException e) {
 465             throw new Fault(i18n, "editJTI.cantFindClass",
 466                             new Object[] { interviewClassName, inFile });
 467         }
 468         catch (InstantiationException e) {
 469             throw new Fault(i18n, "editJTI.cantInstantiateClass",
 470                             new Object[] { interviewClassName, inFile });
 471         }
 472         catch (IllegalAccessException e) {
 473             throw new Fault(i18n, "editJTI.cantAccessClass",
 474                             new Object[] { interviewClassName, inFile });
 475         }
 476         finally {
 477             try { if (in != null) in.close(); } catch (IOException e) {}
 478         }
 479 
 480         interview.load(inFile);
 481         interview.setEdited(false);
 482     }
 483 
 484     /**
 485      * Save the edited configuration in a specified file.
 486      * @param file The file in which to save the configuration
 487      * @throws IOException if there is a problem while writing the file
 488      * @throws Interview.Fault if there is a problem while saving the interview data
 489      */
 490     public void save(File file) throws IOException, Interview.Fault {
 491         interview.save(file);
 492     }
 493 
 494     /**
 495      * Show the current question path for the configuration.
 496      */
 497     public void showPath() {
 498         Question[] path = interview.getPath();
 499 
 500         int indent = 0;
 501         for (int i = 0; i < path.length; i++)
 502             indent = Math.max(indent, path[i].getTag().length());
 503         indent = Math.min(indent, MAX_INDENT);
 504 
 505         for (int i = 0; i < path.length; i++) {
 506             Question q = path[i];
 507             String tag = q.getTag();
 508             String value = q.getStringValue();
 509             out.print(tag);
 510             int l = tag.length();
 511             if (l > MAX_INDENT && value != null && value.length() > 0) {
 512                 out.println();
 513                 l = 0;
 514             }
 515             for (int x = l; x < indent; x++)
 516                 out.print(' ');
 517             out.print(' ');
 518             out.println(value == null ? "" : value);
 519         }
 520     }
 521 
 522     /**
 523      * Write a log of the questions that determine the current configuration.
 524      * @param logFile the file to which to write the log
 525      * @throws IOException if there is a problem while writing the log file
 526      */
 527     public void writeLog(File logFile) throws IOException {
 528         WizPrint wp = new WizPrint(interview);
 529         wp.setShowResponses(true);
 530         wp.setShowResponseTypes(false);
 531         wp.setShowTags(true);
 532         BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile), StandardCharsets.UTF_8));
 533         wp.write(out);
 534     }
 535 
 536     /**
 537      * Apply a series of edits to the current configuration.
 538      * @param cmds the editing commands to be applied
 539      * @throws EditJTI.Fault if there is a problem while applying the edit commands.
 540      * @see #edit(String)
 541      */
 542     public void edit(String[] cmds) throws Fault {
 543         for (int i = 0; i < cmds.length; i++) {
 544             edit(cmds[i]);
 545         }
 546     }
 547 
 548     /**
 549      * Apply an edit to the current configuration.
 550      * @param cmd the editing command to be applied
 551      * Currently, two forms of command are supported: <dl>
 552      * <dt><em>tag-name=value</em>
 553      * <dd>Set the response to the question whose value is <em>tag-name</em> to <em>value</em>
 554      * <dt><em>/search/replace/</em>
 555      * <dd>For all questions on the current path, change instances of <em>search</em> to  <em>replace</em>
 556      * </dl>
 557      * @throws EditJTI.Fault if there is a problem while applying the edit commands.
 558      * @see #edit(String[])
 559      */
 560     public void edit(String cmd) throws Fault {
 561         if (cmd == null || cmd.length() == 0)
 562             return;
 563 
 564         int eqIndex = cmd.indexOf('=');
 565         if (Character.isJavaIdentifierStart(cmd.charAt(0)) && eqIndex > 0)
 566             setValue(cmd.substring(0, eqIndex), cmd.substring(eqIndex + 1));
 567         else if (cmd.toLowerCase().startsWith("import:")) {
 568             importFile(new File(cmd.substring("import:".length())));
 569         }
 570         else {
 571             int left = 0;
 572             // could support a command letter in front?
 573             char delim = cmd.charAt(left);
 574             int center = cmd.indexOf(delim, left + 1);
 575             if (center == -1)
 576                 throw new Fault(i18n, "editJTI.badCmd", cmd);
 577             // could support trailing flags?
 578             int right = cmd.length() - 1;
 579             if (cmd.charAt(right) != delim)
 580                 throw new Fault(i18n, "editJTI.badCmd", cmd);
 581             String searchText = cmd.substring(left + 1, center);
 582             String replaceText = cmd.substring(center + 1, right);
 583             if (searchText.length() == 0)
 584                 throw new Fault(i18n, "editJTI.badCmd", cmd);
 585             setMatchingValues(searchText, replaceText);
 586         }
 587     }
 588 
 589     private void importFile(File file) throws Fault {
 590 
 591         InputStream in;
 592         try {
 593             in = new BufferedInputStream(new FileInputStream(file));
 594         }
 595         catch (FileNotFoundException e) {
 596             throw new Fault(i18n, "editJTI.cantFindImport", file);
 597         }
 598         catch (IOException e) {
 599             throw new Fault(i18n, "editJTI.cantOpenImport",
 600                             new Object[] { file, e });
 601         }
 602 
 603         Properties p;
 604         try {
 605             p = new Properties();
 606             p.load(in);
 607             in.close();
 608         }
 609         catch (IOException e) {
 610             throw new Fault(i18n, "editJTI.cantReadImport",
 611                             new Object[] { file, e });
 612         }
 613         finally {
 614             try { if (in != null) in.close(); } catch (IOException e) {}
 615         }
 616 
 617         // for each question on the path, see if there is a corresponding
 618         // imported value
 619         Question[] path = interview.getPath();
 620         for (int i = 0; i < path.length; i++) {
 621             Question q = path[i];
 622             String v = p.getProperty(q.getTag());
 623             if (v != null) {
 624                 setValue(q, v);
 625                 path = interview.getPath(); // update path in case tail has changed
 626             }
 627         }
 628     }
 629 
 630     private void setMatchingValues(String searchText, String replaceText) throws Fault {
 631         boolean found = false;
 632 
 633         Question[] path = interview.getPath();
 634         for (int i = 0; i < path.length; i++) {
 635             Question q = path[i];
 636             String currValue = q.getStringValue();
 637             if (currValue == null)
 638                 continue;
 639             // currently hardwired: considerCase: false; word match: false
 640             int pos = match(searchText, currValue, false, false);
 641             if (pos >= 0) {
 642                 String newValue = currValue.substring(0, pos)
 643                     + replaceText
 644                     + currValue.substring(pos + searchText.length());
 645                 setValue(q, newValue);
 646                 found = true;
 647                 path = interview.getPath(); // update path in case tail has changed
 648             }
 649         }
 650         if (!found)
 651             throw new Fault(i18n, "editJTI.cantFindMatch", searchText);
 652     }
 653 
 654     private void setValue(String tag, String value) throws Fault {
 655         Question[] path = interview.getPath();
 656         for (int i = 0; i < path.length; i++) {
 657             Question q = path[i];
 658             if (q.getTag().equals(tag)) {
 659                 setValue(q, value);
 660                 return;
 661             }
 662         }
 663         throw new Fault(i18n, "editJTI.cantFindQuestion", tag);
 664     }
 665 
 666     private void setValue(Question q, String value) throws Fault {
 667         try {
 668             String oldValue = q.getStringValue();
 669             q.setValue(value);
 670             if (verbose)
 671                 out.println(i18n.getString("editJTI.update",
 672                     new Object[] { q.getTag(), oldValue, q.getStringValue() }));
 673         }
 674         catch (Interview.Fault e) {
 675             throw new Fault(i18n, "editJTI.cantSetValue", new Object[] { q.getTag(), e.getMessage() } );
 676         }
 677     }
 678 
 679     private static int match(String s1, String s2, boolean considerCase, boolean word) {
 680         int s1len = s1.length();
 681         int s2len = s2.length();
 682         for (int i = 0; i <= s2len - s1len; i++) {
 683             if (s1.regionMatches(!considerCase, 0, s2, i, s1len)) {
 684                 if (!word || (word &&
 685                               ( (i == 0 || isBoundaryCh(s2.charAt(i-1)))
 686                                 && (i+s1len == s2.length() || isBoundaryCh(s2.charAt(i+s1len))) )))
 687                     return i;
 688             }
 689         }
 690         return -1;
 691     }
 692 
 693     private static boolean isBoundaryCh(char c) {
 694         return !(Character.isUnicodeIdentifierStart(c)
 695                  || Character.isUnicodeIdentifierPart(c));
 696     }
 697 
 698     private static void checkUnset(Object item, String option)
 699         throws BadArgs
 700     {
 701         if (item != null)
 702             throw new BadArgs(i18n, "editJTI.dupOption",option);
 703     }
 704 
 705     private InterviewParameters interview;
 706     private boolean verbose;
 707     private PrintWriter out;
 708 
 709     private static int MAX_INDENT = Integer.getInteger("EditJTI.maxIndent", 32).intValue();
 710     private static int NUM_BACKUPS = Integer.getInteger("EditJTI.numBackups", 2).intValue();
 711 
 712     private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(EditJTI.class);
 713 }