/*
* $Id$
*
* Copyright (c) 2001, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.interview;
import java.io.File;
import java.util.ArrayList;
import java.util.Map;
/**
* A {@link Question question} to which the response is one or more filenames.
*/
public abstract class FileListQuestion extends Question
{
/**
* Create a question with a nominated tag.
* @param interview The interview containing this question.
* @param tag A unique tag to identify this specific question.
*/
protected FileListQuestion(Interview interview, String tag) {
super(interview, tag);
if (interview.getInterviewSemantics() > Interview.SEMANTIC_PRE_32)
clear();
setDefaultValue(value);
}
/**
* Get the default response for this question.
* @return the default response for this question.
*
* @see #setDefaultValue
*/
public File[] getDefaultValue() {
return defaultValue;
}
/**
* Set the default response for this question,
* used by the clear method.
* @param v the default response for this question.
*
* @see #getDefaultValue
*/
public void setDefaultValue(File[] v) {
defaultValue = v;
}
/**
* Specify whether or not duplicates should be allowed in the list.
* By default, duplicates are allowed.
* @param b true if duplicates should be allowed, and false otherwise
* @see #isDuplicatesAllowed
*/
public void setDuplicatesAllowed(boolean b) {
duplicatesAllowed = b;
}
/**
* Check whether or not duplicates should be allowed in the list.
* @return true if duplicates should be allowed, and false otherwise
* @see #setDuplicatesAllowed
*/
public boolean isDuplicatesAllowed() {
return duplicatesAllowed;
}
/**
* Get the current (default or latest) response to this question.
* @return The current value.
* @see #setValue
*/
public File[] getValue() {
return value;
}
/**
* Verify this question is on the current path, and if it is,
* return the current value.
* @return the current value of this question
* @throws Interview.NotOnPathFault if this question is not on the
* current path
* @see #getValue
*/
public File[] getValueOnPath()
throws Interview.NotOnPathFault
{
interview.verifyPathContains(this);
return getValue();
}
@Override
public String getStringValue() {
return join(value);
}
/**
* Set the response to this question to the value represented by
* a string-valued argument.
* @param paths The new value for the question, can be null to set no value.
* @see #getValue
*/
@Override
public void setValue(String paths) {
setValue(paths == null ? null : split(paths));
}
/**
* Set the current value.
* @param newValue The value to be set.
* @see #getValue
*/
public void setValue(File[] newValue) {
File[] oldValue = value;
value = newValue;
if (!equal(value, oldValue)) {
interview.updatePath(this);
interview.setEdited(true);
}
}
/**
* Simple validation, upgrade if needed.
* Iterates values, checks against filters, except if interview semantics
* are set to an pre-50 version, in which case true is always returned.
* Using semantics greater than 50 is highly recommended and recommended if
* an old interview is being modernized.
* @return False if any values are rejected by filters, true otherwise.
* True if there are no values or no filters.
* @see com.sun.interview.Interview#getInterviewSemantics
* @see com.sun.interview.Interview#SEMANTIC_VERSION_50
*/
@Override
public boolean isValueValid() {
if (interview.getInterviewSemantics() < Interview.SEMANTIC_VERSION_50) {
// not that useful, but it's how the original question behaved
return true;
}
if (value == null || value.length == 0 ||
filters == null || filters.length == 0) {
return true;
}
for (File f: value) {
if (f == null) {
continue;
}
for (FileFilter fs: filters) {
if (fs != null && !fs.accept(f)) {
return false;
}
}
} // for
return true;
}
@Override
public boolean isValueAlwaysValid() {
return false;
}
/**
* Get the filters used to select valid files for a response
* to this question.
* @return An array of filters
* @see #setFilter
* @see #setFilters
*/
public FileFilter[] getFilters() {
return filters;
}
/**
* Set a filter used to select valid files for a response
* to this question.
* @param filter a filter used to select valid files for a response
* to this question
* @see #getFilters
* @see #setFilters
*/
public void setFilter(FileFilter filter) {
filters = new FileFilter[] { filter };
}
/**
* Set the filters used to select valid files for a response
* to this question. For pre-50 behavior, both the filters and the hint
* filter values are treated the same, and neither is used for validation
* (e.g. isValid()
.
* @param fs An array of filters used to select valid files for a response
* to this question
* @see #getFilters
* @see #setFilters
* @see #getHintFilters
*/
public void setFilters(FileFilter[] fs) {
if (interview.getInterviewSemantics() >= Interview.SEMANTIC_VERSION_50) {
filters = fs;
}
else {
// old behavior, the fitlers act as hint filters, not validation
// filters
filters = hintFilters = fs;
}
}
/**
* Set the filters which the user can use to help find files among a list
* of files - this is somewhat exposing of the fact that there is a user
* interface. This should not be confused with setFilters(), which in
* version 5.0 or later of the harness, are used to do validity checks on
* the actual value (e.g.. in isValid()
.
* @param fs Filters which might be offered to the user.
* @see #setFilters
* @see #isValueValid
* @since 5.0
*/
public void setHintFilters(FileFilter[] fs) {
hintFilters = fs;
}
/**
* A set of filters to help users locate the right file/dir.
* These filters are not used for validating the question value.
* @see #setHintFilters(com.sun.interview.FileFilter[])
* @see #getFilters
* @since 5.0
*/
public FileFilter[] getHintFilters() {
if (interview.getInterviewSemantics() >= Interview.SEMANTIC_VERSION_50) {
return hintFilters;
} else {
return filters;
}
}
/**
* Get the default directory for files for a response to this question.
* @return the default directory in which files should be found/placed
* @see #setBaseDirectory
* @see #isBaseRelativeOnly
*/
public File getBaseDirectory() {
return baseDir;
}
/**
* Set the default directory for files for a response to this question.
* @param dir the default directory in which files should be found/placed
* @see #getBaseDirectory
*/
public void setBaseDirectory(File dir) {
baseDir = dir;
}
/**
* Determine whether all valid responses to this question should be
* relative to the base directory (i.e. in or under it.)
* @return true if all valid responses to this question should be
* relative to the base directory
* @see #setBaseRelativeOnly
*/
public boolean isBaseRelativeOnly() {
return baseRelativeOnly;
}
/**
* Specify whether all valid responses to this question should be
* relative to the base directory (i.e. in or under it.)
* @param b this parameter should be true if all valid responses
* to this question should be relative to the base directory
* @see #setBaseRelativeOnly
*/
public void setBaseRelativeOnly(boolean b) {
baseRelativeOnly = b;
}
/**
* Clear any response to this question, resetting the value
* back to its initial state.
*/
public void clear() {
setValue(defaultValue);
}
/**
* Load the value for this question from a dictionary, using
* the tag as the key.
* @param data The map from which to load the value for this question.
*/
protected void load(Map data) {
Object o = data.get(tag);
if (o instanceof File[])
setValue((File[])o);
else if (o instanceof String)
setValue(split((String)o));
}
/**
* Break apart a string containing a white-space separate list of file
* names into an array of individual files.
* If the string is null or empty, an empty array is returned.
* The preferred separator is a newline character;
* if there are no newline characters in the string, then
* (for backward compatibility) space is accepted instead.
* @param s The string to be broken apart
* @return An array of files determined from the parameter string.
* @see #join
*/
public static File[] split(String s) {
if (s == null)
return empty;
char sep = (s.indexOf('\n') == -1 ? ' ' : '\n');
ArrayList v = new ArrayList<>();
int start = -1;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == sep) {
if (start != -1)
v.add(new File(s.substring(start, i)));
start = -1;
} else
if (start == -1)
start = i;
}
if (start != -1)
v.add(new File(s.substring(start)));
if (v.size() == 0)
return empty;
File[] a = new File[v.size()];
v.toArray(a);
return a;
}
private static final File[] empty = { };
/**
* Save the value for this question in a dictionary, using
* the tag as the key.
* @param data The map in which to save the value for this question.
*/
protected void save(Map data) {
if (value != null)
data.put(tag, join(value));
}
/**
* Convert a list of filenames to a newline separated string.
* @param ff an array of filenames
* @return a string containing the filenames separated by newline
* characters.
* If there is just one filename, and if it contains space characters
* in its path,
* the list is terminated by a newline as well.
* If the parameter array is null or empty, an empty string is returned.
* @see #split
*/
public static String join(File[] ff) {
if (ff == null || ff.length == 0)
return "";
int l = ff.length - 1; // allow for spaces between words
for (int i = 0; i < ff.length; i++)
l += ff[i].getPath().length();
StringBuffer sb = new StringBuffer(l);
String ff0p = ff[0].getPath();
sb.append(ff0p);
if (ff.length == 1 && ff0p.indexOf(' ') != -1) {
// if there is just one file, and if it contains space characters,
// then force a newline character for subsequent split to recognize
sb.append('\n');
}
else {
// if there is more than one file, separate them with newlines
for (int i = 1; i < ff.length; i++) {
sb.append('\n');
sb.append(ff[i].getPath());
}
}
return sb.toString();
}
/**
* Determine if two arrays of filenames are equal.
* @param f1 the first array to be compared
* @param f2 the other array to be compared
* @return true if both arrays are null, or if neither are null and if
* their contents match, element for element, in order
*/
protected static boolean equal(File[] f1, File[] f2) {
if (f1 == null || f2 == null)
return (f1 == f2);
if (f1.length != f2.length)
return false;
for (int i = 0; i < f1.length; i++) {
if (f1[i] != f2[i])
return false;
}
return true;
}
/**
* The current (default or latest) response to this question.
*/
protected File[] value;
/**
* The default response for this question.
*/
private File[] defaultValue;
private File baseDir;
private boolean baseRelativeOnly;
private FileFilter[] filters;
private FileFilter[] hintFilters;
private boolean duplicatesAllowed = true;
}