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.interview; 28 29 import java.io.File; 30 import java.util.ArrayList; 31 import java.util.Map; 32 33 /** 34 * A {@link Question question} to which the response is one or more filenames. 35 */ 36 public abstract class FileListQuestion extends Question 37 { 38 /** 39 * Create a question with a nominated tag. 40 * @param interview The interview containing this question. 41 * @param tag A unique tag to identify this specific question. 42 */ 43 protected FileListQuestion(Interview interview, String tag) { 44 super(interview, tag); 45 46 if (interview.getInterviewSemantics() > Interview.SEMANTIC_PRE_32) 47 clear(); 48 49 setDefaultValue(value); 50 } 51 52 /** 53 * Get the default response for this question. 54 * @return the default response for this question. 55 * 56 * @see #setDefaultValue 57 */ 58 public File[] getDefaultValue() { 59 return defaultValue; 60 } 61 62 /** 63 * Set the default response for this question, 64 * used by the clear method. 65 * @param v the default response for this question. 66 * 67 * @see #getDefaultValue 68 */ 69 public void setDefaultValue(File[] v) { 70 defaultValue = v; 71 } 72 73 /** 74 * Specify whether or not duplicates should be allowed in the list. 75 * By default, duplicates are allowed. 76 * @param b true if duplicates should be allowed, and false otherwise 77 * @see #isDuplicatesAllowed 78 */ 79 public void setDuplicatesAllowed(boolean b) { 80 duplicatesAllowed = b; 81 } 82 83 /** 84 * Check whether or not duplicates should be allowed in the list. 85 * @return true if duplicates should be allowed, and false otherwise 86 * @see #setDuplicatesAllowed 87 */ 88 public boolean isDuplicatesAllowed() { 89 return duplicatesAllowed; 90 } 91 92 /** 93 * Get the current (default or latest) response to this question. 94 * @return The current value. 95 * @see #setValue 96 */ 97 public File[] getValue() { 98 return value; 99 } 100 101 /** 102 * Verify this question is on the current path, and if it is, 103 * return the current value. 104 * @return the current value of this question 105 * @throws Interview.NotOnPathFault if this question is not on the 106 * current path 107 * @see #getValue 108 */ 109 public File[] getValueOnPath() 110 throws Interview.NotOnPathFault 111 { 112 interview.verifyPathContains(this); 113 return getValue(); 114 } 115 116 @Override 117 public String getStringValue() { 118 return join(value); 119 } 120 121 /** 122 * Set the response to this question to the value represented by 123 * a string-valued argument. 124 * @param paths The new value for the question, can be null to set no value. 125 * @see #getValue 126 */ 127 @Override 128 public void setValue(String paths) { 129 setValue(paths == null ? null : split(paths)); 130 } 131 132 /** 133 * Set the current value. 134 * @param newValue The value to be set. 135 * @see #getValue 136 */ 137 public void setValue(File[] newValue) { 138 File[] oldValue = value; 139 value = newValue; 140 if (!equal(value, oldValue)) { 141 interview.updatePath(this); 142 interview.setEdited(true); 143 } 144 } 145 146 /** 147 * Simple validation, upgrade if needed. 148 * Iterates values, checks against filters, except if interview semantics 149 * are set to an pre-50 version, in which case true is always returned. 150 * Using semantics greater than 50 is highly recommended and recommended if 151 * an old interview is being modernized. 152 * @return False if any values are rejected by filters, true otherwise. 153 * True if there are no values or no filters. 154 * @see com.sun.interview.Interview#getInterviewSemantics 155 * @see com.sun.interview.Interview#SEMANTIC_VERSION_50 156 */ 157 @Override 158 public boolean isValueValid() { 159 if (interview.getInterviewSemantics() < Interview.SEMANTIC_VERSION_50) { 160 // not that useful, but it's how the original question behaved 161 return true; 162 } 163 164 if (value == null || value.length == 0 || 165 filters == null || filters.length == 0) { 166 return true; 167 } 168 169 for (File f: value) { 170 if (f == null) { 171 continue; 172 } 173 174 for (FileFilter fs: filters) { 175 if (fs != null && !fs.accept(f)) { 176 return false; 177 } 178 } 179 } // for 180 181 return true; 182 } 183 184 @Override 185 public boolean isValueAlwaysValid() { 186 return false; 187 } 188 189 /** 190 * Get the filters used to select valid files for a response 191 * to this question. 192 * @return An array of filters 193 * @see #setFilter 194 * @see #setFilters 195 */ 196 public FileFilter[] getFilters() { 197 return filters; 198 } 199 200 /** 201 * Set a filter used to select valid files for a response 202 * to this question. 203 * @param filter a filter used to select valid files for a response 204 * to this question 205 * @see #getFilters 206 * @see #setFilters 207 */ 208 public void setFilter(FileFilter filter) { 209 filters = new FileFilter[] { filter }; 210 } 211 212 /** 213 * Set the filters used to select valid files for a response 214 * to this question. For pre-50 behavior, both the filters and the hint 215 * filter values are treated the same, and neither is used for validation 216 * (e.g. <code>isValid()</code>. 217 * @param fs An array of filters used to select valid files for a response 218 * to this question 219 * @see #getFilters 220 * @see #setFilters 221 * @see #getHintFilters 222 */ 223 public void setFilters(FileFilter[] fs) { 224 if (interview.getInterviewSemantics() >= Interview.SEMANTIC_VERSION_50) { 225 filters = fs; 226 } 227 else { 228 // old behavior, the fitlers act as hint filters, not validation 229 // filters 230 filters = hintFilters = fs; 231 } 232 } 233 234 /** 235 * Set the filters which the user can use to help find files among a list 236 * of files - this is somewhat exposing of the fact that there is a user 237 * interface. This should not be confused with setFilters(), which in 238 * version 5.0 or later of the harness, are used to do validity checks on 239 * the actual value (e.g.. in <code>isValid()</code>. 240 * @param fs Filters which might be offered to the user. 241 * @see #setFilters 242 * @see #isValueValid 243 * @since 5.0 244 */ 245 public void setHintFilters(FileFilter[] fs) { 246 hintFilters = fs; 247 } 248 249 /** 250 * A set of filters to help users locate the right file/dir. 251 * These filters are not used for validating the question value. 252 * @see #setHintFilters(com.sun.interview.FileFilter[]) 253 * @see #getFilters 254 * @since 5.0 255 */ 256 public FileFilter[] getHintFilters() { 257 if (interview.getInterviewSemantics() >= Interview.SEMANTIC_VERSION_50) { 258 return hintFilters; 259 } else { 260 return filters; 261 } 262 } 263 264 /** 265 * Get the default directory for files for a response to this question. 266 * @return the default directory in which files should be found/placed 267 * @see #setBaseDirectory 268 * @see #isBaseRelativeOnly 269 */ 270 public File getBaseDirectory() { 271 return baseDir; 272 } 273 274 /** 275 * Set the default directory for files for a response to this question. 276 * @param dir the default directory in which files should be found/placed 277 * @see #getBaseDirectory 278 */ 279 public void setBaseDirectory(File dir) { 280 baseDir = dir; 281 } 282 283 /** 284 * Determine whether all valid responses to this question should be 285 * relative to the base directory (i.e. in or under it.) 286 * @return true if all valid responses to this question should be 287 * relative to the base directory 288 * @see #setBaseRelativeOnly 289 */ 290 public boolean isBaseRelativeOnly() { 291 return baseRelativeOnly; 292 } 293 294 /** 295 * Specify whether all valid responses to this question should be 296 * relative to the base directory (i.e. in or under it.) 297 * @param b this parameter should be true if all valid responses 298 * to this question should be relative to the base directory 299 * @see #setBaseRelativeOnly 300 */ 301 public void setBaseRelativeOnly(boolean b) { 302 baseRelativeOnly = b; 303 } 304 305 /** 306 * Clear any response to this question, resetting the value 307 * back to its initial state. 308 */ 309 public void clear() { 310 setValue(defaultValue); 311 } 312 313 /** 314 * Load the value for this question from a dictionary, using 315 * the tag as the key. 316 * @param data The map from which to load the value for this question. 317 */ 318 protected void load(Map<String, String> data) { 319 Object o = data.get(tag); 320 if (o instanceof File[]) 321 setValue((File[])o); 322 else if (o instanceof String) 323 setValue(split((String)o)); 324 } 325 326 /** 327 * Break apart a string containing a white-space separate list of file 328 * names into an array of individual files. 329 * If the string is null or empty, an empty array is returned. 330 * The preferred separator is a newline character; 331 * if there are no newline characters in the string, then 332 * (for backward compatibility) space is accepted instead. 333 * @param s The string to be broken apart 334 * @return An array of files determined from the parameter string. 335 * @see #join 336 */ 337 public static File[] split(String s) { 338 if (s == null) 339 return empty; 340 341 char sep = (s.indexOf('\n') == -1 ? ' ' : '\n'); 342 343 ArrayList<File> v = new ArrayList<>(); 344 int start = -1; 345 for (int i = 0; i < s.length(); i++) { 346 if (s.charAt(i) == sep) { 347 if (start != -1) 348 v.add(new File(s.substring(start, i))); 349 start = -1; 350 } else 351 if (start == -1) 352 start = i; 353 } 354 if (start != -1) 355 v.add(new File(s.substring(start))); 356 if (v.size() == 0) 357 return empty; 358 File[] a = new File[v.size()]; 359 v.toArray(a); 360 return a; 361 } 362 363 private static final File[] empty = { }; 364 365 /** 366 * Save the value for this question in a dictionary, using 367 * the tag as the key. 368 * @param data The map in which to save the value for this question. 369 */ 370 protected void save(Map<String, String> data) { 371 if (value != null) 372 data.put(tag, join(value)); 373 } 374 375 /** 376 * Convert a list of filenames to a newline separated string. 377 * @param ff an array of filenames 378 * @return a string containing the filenames separated by newline 379 * characters. 380 * If there is just one filename, and if it contains space characters 381 * in its path, 382 * the list is terminated by a newline as well. 383 * If the parameter array is null or empty, an empty string is returned. 384 * @see #split 385 */ 386 public static String join(File[] ff) { 387 if (ff == null || ff.length == 0) 388 return ""; 389 390 int l = ff.length - 1; // allow for spaces between words 391 for (int i = 0; i < ff.length; i++) 392 l += ff[i].getPath().length(); 393 394 StringBuffer sb = new StringBuffer(l); 395 396 String ff0p = ff[0].getPath(); 397 sb.append(ff0p); 398 399 if (ff.length == 1 && ff0p.indexOf(' ') != -1) { 400 // if there is just one file, and if it contains space characters, 401 // then force a newline character for subsequent split to recognize 402 sb.append('\n'); 403 } 404 else { 405 // if there is more than one file, separate them with newlines 406 for (int i = 1; i < ff.length; i++) { 407 sb.append('\n'); 408 sb.append(ff[i].getPath()); 409 } 410 } 411 412 return sb.toString(); 413 } 414 415 /** 416 * Determine if two arrays of filenames are equal. 417 * @param f1 the first array to be compared 418 * @param f2 the other array to be compared 419 * @return true if both arrays are null, or if neither are null and if 420 * their contents match, element for element, in order 421 */ 422 protected static boolean equal(File[] f1, File[] f2) { 423 if (f1 == null || f2 == null) 424 return (f1 == f2); 425 426 if (f1.length != f2.length) 427 return false; 428 429 for (int i = 0; i < f1.length; i++) { 430 if (f1[i] != f2[i]) 431 return false; 432 } 433 434 return true; 435 } 436 437 /** 438 * The current (default or latest) response to this question. 439 */ 440 protected File[] value; 441 442 /** 443 * The default response for this question. 444 */ 445 private File[] defaultValue; 446 447 private File baseDir; 448 449 private boolean baseRelativeOnly; 450 451 private FileFilter[] filters; 452 private FileFilter[] hintFilters; 453 454 private boolean duplicatesAllowed = true; 455 }