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), new Object[] {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), new Object[] {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 }