1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2011, 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.interview;
  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.FileOutputStream;
  34 import java.io.FileNotFoundException;
  35 import java.io.InputStream;
  36 import java.io.OutputStream;
  37 import java.io.IOException;
  38 import java.io.PrintStream;
  39 import java.lang.reflect.InvocationTargetException;
  40 import java.text.MessageFormat;
  41 import java.util.*;
  42 
  43 /**
  44  * An API (with a basic front-end application) for batch editing an
  45  * interview.
  46  */
  47 public class WizEdit
  48 {
  49     /**
  50      * This exception is used to indicate a problem with the command line arguments.
  51      */
  52 
  53     public static class BadArgs extends Exception
  54     {
  55         /**
  56          * Create a BadArgs exception.
  57          * @param i18n A resource bundle in which to find the detail message.
  58          * @param s The key for the detail message.
  59          */
  60         BadArgs(ResourceBundle i18n, String s) {
  61             super(i18n.getString(s));
  62         }
  63 
  64         /**
  65          * Create a BadArgs exception.
  66          * @param i18n A resource bundle in which to find the detail message.
  67          * @param s The key for the detail message.
  68          * @param o An argument to be formatted with the detail message by
  69          * {@link java.text.MessageFormat#format}
  70          */
  71         BadArgs(ResourceBundle i18n, String s, Object o) {
  72             super(MessageFormat.format(i18n.getString(s), o));
  73         }
  74 
  75 
  76         /**
  77          * Create a BadArgs exception.
  78          * @param i18n A resource bundle in which to find the detail message.
  79          * @param s The key for the detail message.
  80          * @param o An array of arguments to be formatted with the detail message by
  81          * {@link java.text.MessageFormat#format}
  82          */
  83         BadArgs(ResourceBundle i18n, String s, Object[] o) {
  84             super(MessageFormat.format(i18n.getString(s), o));
  85         }
  86     }
  87 
  88     /**
  89      * This exception is to report problems that occur while editing
  90      * the responses to questions in an interview.
  91      */
  92     public static class Fault extends Exception
  93     {
  94         /**
  95          * Create a Fault.
  96          * @param i18n A resource bundle in which to find the detail message.
  97          * @param s The key for the detail message.
  98          */
  99         Fault(ResourceBundle i18n, String s) {
 100             super(i18n.getString(s));
 101         }
 102 
 103         /**
 104          * Create a Fault.
 105          * @param i18n A resource bundle in which to find the detail message.
 106          * @param s The key for the detail message.
 107          * @param o An argument to be formatted with the detail message by
 108          * {@link java.text.MessageFormat#format}
 109          */
 110         Fault(ResourceBundle i18n, String s, Object o) {
 111             super(MessageFormat.format(i18n.getString(s), o));
 112         }
 113 
 114         /**
 115          * Create a Fault.
 116          * @param i18n A resource bundle in which to find the detail message.
 117          * @param s The key for the detail message.
 118          * @param o An array of arguments to be formatted with the detail message by
 119          * {@link java.text.MessageFormat#format}
 120          */
 121         Fault(ResourceBundle i18n, String s, Object[] o) {
 122             super(MessageFormat.format(i18n.getString(s), o));
 123         }
 124     }
 125 
 126     /**
 127      * Simple command-line front-end to the facilities of the API.
 128      * @param args Command line arguments.
 129      */
 130     public static void main(String[] args) {
 131         try {
 132             Vector<String> v = new Vector<>();
 133             File interviewFile = null;
 134             File outFileName = null;
 135 
 136             for (int i = 0; i < args.length; i++) {
 137                 if (args[i].equals("-o") && i + 1 < args.length)
 138                     outFileName = new File(args[++i]);
 139                 else if (args[i].equals("-e"))
 140                     v.addElement(args[++i]);
 141                 else if (args[i].startsWith("-"))
 142                     throw new BadArgs(i18n, "edit.badOption", args[i]);
 143                 else if (i == args.length - 1 && args[i].endsWith(".jti"))
 144                     interviewFile = new File(args[i]);
 145                 else
 146                     throw new BadArgs(i18n, "edit.badOption", args[i]);
 147             }
 148 
 149             if (interviewFile == null)
 150                 throw new BadArgs(i18n, "edit.noInterview");
 151 
 152             Interview interview;
 153 
 154             try {
 155                 InputStream in = new BufferedInputStream(new FileInputStream(interviewFile));
 156                 Map<String, String> stringProps = com.sun.javatest.util.Properties.load(in);
 157                 String interviewClassName = stringProps.get("INTERVIEW");
 158                 if (interviewClassName == null)
 159                     throw new Fault(i18n, "edit.noInterview");
 160                 Class<? extends Interview> ic = Class.forName(interviewClassName).asSubclass(Interview.class);
 161                 interview = ic.getDeclaredConstructor().newInstance();
 162                 interview.load(stringProps, false);
 163             }
 164             catch (FileNotFoundException e) {
 165                 throw new Fault(i18n, "edit.cantFindFile", interviewFile);
 166             }
 167             catch (IOException e) {
 168                 throw new Fault(i18n, "edit.cantReadFile", e);
 169             }
 170 
 171             String[] cmds = new String[v.size()];
 172             v.copyInto(cmds);
 173 
 174             WizEdit editor = new WizEdit(interview);
 175             editor.edit(cmds);
 176 
 177             try {
 178                 OutputStream out = new BufferedOutputStream(new FileOutputStream(outFileName));
 179                 Properties p = new Properties();
 180                 interview.save(com.sun.javatest.util.Properties.convertToStringProps(p));
 181                 p.store(out, "Interview: " + interview.getTitle());
 182             }
 183             catch (IOException e) {
 184                 throw new Fault(i18n, "edit.cantWriteFile", e);
 185             }
 186         }
 187         catch (BadArgs e) {
 188             System.err.println("Error: " + e.getMessage());
 189             //usage();
 190             System.exit(1);
 191         }
 192         catch (Interview.Fault e) {
 193             System.err.println("Problem reading file: " + e);
 194             System.exit(2);
 195         }
 196         catch (ClassNotFoundException e) {
 197             System.err.println("Problem reading file: the interview could not be loaded because some classes that are required by the interview were not found on your classpath. The specific exception that occurred was: " + e);
 198             System.exit(2);
 199         }
 200         catch (IllegalAccessException e) {
 201             System.err.println("Problem reading file: the interview could not be loaded because some classes that are required by the interview caused access violations. The specific exception that occurred was: " + e);
 202             System.exit(2);
 203         }
 204         catch (InstantiationException | NoSuchMethodException | InvocationTargetException e) {
 205             System.err.println("Problem reading file: the interview could not be loaded because some classes that are required by the interview could not be instantiated. The specific exception that occurred was: " + e);
 206             System.exit(2);
 207         }
 208         catch (Fault e) {
 209             System.err.println(e.getMessage());
 210             System.exit(2);
 211         }
 212     }
 213 
 214     /**
 215      * Create an editor for the questions in an interview.
 216      * @param interview The interview containing the responses to be edited.
 217      */
 218     public WizEdit(Interview interview) {
 219         this.interview = interview;
 220     }
 221 
 222     /**
 223      * Set whether or not the edit should be done verbosely.
 224      * @param verbose Set to true for verbose mode, and false otherwise.
 225      * @see #setVerbose(boolean, PrintStream)
 226      */
 227     public void setVerbose(boolean verbose) {
 228         this.verbose = verbose;
 229     }
 230 
 231     /**
 232      * Set whether or not the edit should be done verbosely,
 233      * and set the stream to which tracing information should be output.
 234      * @param verbose Set to true for verbose mode, and false otherwise.
 235      * @param out The stream to which verbose output should be directed.
 236      */
 237     public void setVerbose(boolean verbose, PrintStream out) {
 238         this.verbose = verbose;
 239         this.out = out;
 240     }
 241 
 242     /**
 243      * Apply a series of edits to the set of responses in an interview.
 244      * The edits are applied one at a time, and as each edit is applied,
 245      * the set of questions in the current interview path may change:
 246      * specifically, the set of questions after the one that is edited
 247      * may change.
 248      * @param cmds A set of editing commands to apply to the responses.
 249      * @throws WizEdit.Fault if there is a problem while applying the edits.
 250      * @see #edit(String)
 251      */
 252     public void edit(String[] cmds) throws Fault {
 253         for (int i = 0; i < cmds.length; i++)
 254             edit(cmds[i]);
 255     }
 256 
 257     /**
 258      * Apply an edit to the set of responses in an interview.
 259      * After the edit is applied, the set of questions in the
 260      * current interview path may change: specifically, the set
 261      * of questions after the one that is edited may change.
 262      * @param cmd An edit command to apply to the responses.
 263      * @throws WizEdit.Fault if there is a problem while applying the edit.
 264      * @see #edit(String[])
 265      */
 266     public void edit(String cmd) throws Fault {
 267         if (cmd == null || cmd.length() == 0)
 268             throw new Fault(i18n, "edit.nullCmd");
 269         char delim = cmd.charAt(0);
 270         int left = 0;
 271         int center = cmd.indexOf(delim, left+1);
 272         if (center == -1)
 273             throw new Fault(i18n, "edit.badCmd", cmd);
 274         int right = cmd.indexOf(delim, center+1);
 275         String searchText = cmd.substring(left+1, center);
 276         String replaceText = cmd.substring(center+1, right);
 277         if (searchText.length() == 0)
 278             throw new Fault(i18n, "edit.badCmd", cmd);
 279 
 280         Map<String, String> answers = new Hashtable<>();
 281         interview.save(answers);
 282 
 283         Question[] path = interview.getPath();
 284         for (int i = 0; i < path.length; i++) {
 285             Question q = path[i];
 286             try {
 287                 String answer = answers.get(q.getTag());
 288                 if (answer == null)
 289                     continue;
 290                 // // currently hardwired: considerCase: false; word match: false
 291                 // int pos = match(searchText, answer, false, false);
 292                 // if (pos >= 0) {
 293                 //     String newAnswer = answer.substring(0, pos)
 294                 //      + replaceText
 295                 //      + answer.substring(pos+searchText.length());
 296                 if (answer.equalsIgnoreCase(searchText)) {
 297                     String newAnswer = replaceText;
 298                     q.setValue(newAnswer);
 299 
 300                     if (verbose) {
 301                         Map<String, String> h = new Hashtable<>();
 302                         q.save(h);
 303                         out.println("Question:     " + q.getSummary());
 304                         out.println("changed from: " + answer);
 305                         out.println("          to: " + h.get(q.getTag()));
 306                     }
 307                 }
 308             }
 309             catch (Interview.Fault e) {
 310                 throw new Fault(i18n, "edit.cantSetValue",
 311                                 new Object[] { q.getSummary(), e.getMessage() });
 312             }
 313         }
 314 
 315     }
 316 
 317     private static int match(String s1, String s2, boolean considerCase, boolean word) {
 318         int s1len = s1.length();
 319         int s2len = s2.length();
 320         for (int i = 0; i <= s2len - s1len; i++) {
 321             if (s1.regionMatches(!considerCase, 0, s2, i, s1len)) {
 322                 if (!word || (word &&
 323                               ( (i == 0 || isBoundaryCh(s2.charAt(i-1)))
 324                                 && (i+s1len == s2.length() || isBoundaryCh(s2.charAt(i+s1len))) )))
 325                     return i;
 326             }
 327         }
 328         return -1;
 329     }
 330 
 331     private static boolean isBoundaryCh(char c) {
 332         return !(Character.isUnicodeIdentifierStart(c)
 333                  || Character.isUnicodeIdentifierPart(c));
 334     }
 335 
 336     private Interview interview;
 337     private boolean considerCase = false;
 338     private boolean word = false;
 339     private boolean verbose;
 340     private PrintStream out = System.err;
 341 
 342     private static final ResourceBundle i18n = Interview.i18n;
 343 }