use of org.apache.sis.util.logging.WarningListeners in project sis by apache.
the class ChannelFactory method prepare.
/**
* Returns a byte channel factory from the given input or output,
* or {@code null} if the given input/output is of unknown type.
* More specifically:
*
* <ul>
* <li>If the given storage is {@code null}, then this method returns {@code null}.</li>
* <li>If the given storage is a {@link ReadableByteChannel} or an {@link InputStream},
* then the factory will return that input directly or indirectly as a wrapper.</li>
* <li>If the given storage is a {@link WritableByteChannel} or an {@link OutputStream}
* and the {@code allowWriteOnly} argument is {@code true},
* then the factory will return that output directly or indirectly as a wrapper.</li>
* <li>If the given storage if a {@link Path}, {@link File}, {@link URL}, {@link URI}
* or {@link CharSequence}, then the factory will open new channels on demand.</li>
* </ul>
*
* The given options are used for opening the channel on a <em>best effort basis</em>.
* In particular, even if the caller provided the {@code WRITE} option, he still needs
* to verify if the returned channel implements {@link java.nio.channels.WritableByteChannel}.
* This is because the channel may be opened by {@link URL#openStream()}, in which case the
* options are ignored.
*
* <p>The following options are illegal for read operations and will cause an exception to be
* thrown if provided while {@code allowWriteOnly} is {@code false}:
* {@code APPEND}, {@code TRUNCATE_EXISTING}, {@code DELETE_ON_CLOSE}. We reject those options
* because this method is primarily designed for readable channels, with optional data edition.
* Since the write option is not guaranteed to be honored, we have to reject the options that
* would alter significatively the channel behavior depending on whether we have been able to
* honor the options or not.</p>
*
* @param storage the stream or the file to open, or {@code null}.
* @param encoding if the input is an encoded URL, the character encoding (normally {@code "UTF-8"}).
* If the URL is not encoded, then {@code null}. This argument is ignored if the given
* input does not need to be converted from URL to {@code File}.
* @param allowWriteOnly whether to allow wrapping {@link WritableByteChannel} and {@link OutputStream}.
* @param options the options to use for creating a new byte channel. Can be null or empty for read-only.
* @return the channel factory for the given input, or {@code null} if the given input is of unknown type.
* @throws IOException if an error occurred while processing the given input.
*/
public static ChannelFactory prepare(Object storage, final String encoding, final boolean allowWriteOnly, OpenOption... options) throws IOException {
/*
* Unconditionally verify the options (unless 'allowWriteOnly' is true),
* even if we may not use them.
*/
final Set<OpenOption> optionSet;
if (options == null || options.length == 0) {
optionSet = Collections.singleton(StandardOpenOption.READ);
} else {
optionSet = new HashSet<>(Arrays.asList(options));
optionSet.add(StandardOpenOption.READ);
if (!allowWriteOnly && optionSet.removeAll(ILLEGAL_OPTIONS)) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "options", Arrays.toString(options)));
}
}
/*
* Check for inputs that are already readable channels or input streams.
* Note that Channels.newChannel(InputStream) checks for instances of FileInputStream in order to delegate
* to its getChannel() method, but only if the input stream type is exactly FileInputStream, not a subtype.
* If Apache SIS defines its own FileInputStream subclass someday, we may need to add a special case here.
*/
if (storage instanceof ReadableByteChannel || (allowWriteOnly && storage instanceof WritableByteChannel)) {
return new Stream((Channel) storage);
} else if (storage instanceof InputStream) {
return new Stream(Channels.newChannel((InputStream) storage));
} else if (allowWriteOnly && storage instanceof OutputStream) {
return new Stream(Channels.newChannel((OutputStream) storage));
}
/*
* In the following cases, we will try hard to convert to Path objects before to fallback
* on File, URL or URI, because only Path instances allow us to use the given OpenOptions.
*/
if (storage instanceof URL) {
try {
storage = IOUtilities.toPath((URL) storage, encoding);
} catch (IOException e) {
/*
* This is normal if the URL uses HTTP or FTP protocol for instance. Log the exception at FINE
* level without stack trace. We will open the channel later using the URL instead than the Path.
*/
recoverableException(e);
}
} else if (storage instanceof URI) {
/*
* If the user gave us a URI, try to convert to a Path before to fallback to URL, in order to be
* able to use the given OpenOptions. Note that the conversion to Path is likely to fail if the
* URL uses HTTP or FTP protocols, because JDK7 does not provide file systems for them by default.
*/
final URI uri = (URI) storage;
if (!uri.isAbsolute()) {
/*
* All methods invoked in this block throws IllegalArgumentException if the URI has no scheme,
* so we are better to check now and provide a more appropriate exception for this method.
*/
throw new IOException(Resources.format(Resources.Keys.MissingSchemeInURI_1, uri));
} else
try {
storage = Paths.get(uri);
} catch (IllegalArgumentException | FileSystemNotFoundException e) {
try {
storage = uri.toURL();
} catch (MalformedURLException ioe) {
ioe.addSuppressed(e);
throw ioe;
}
/*
* We have been able to convert to URL, but the given OpenOptions may not be used.
* Log the exception at FINE level without stack trace, because the exception is
* probably a normal behavior in this context.
*/
recoverableException(e);
}
} else {
if (storage instanceof CharSequence) {
// Needs to be before the check for File or URL.
storage = IOUtilities.toFileOrURL(storage.toString(), encoding);
}
/*
* If the input is a File or a CharSequence that we have been able to convert to a File,
* try to convert to a Path in order to be able to use the OpenOptions. Only if we fail
* to convert to a Path (which is unlikely), we will use directly the File.
*/
if (storage instanceof File) {
final File file = (File) storage;
try {
storage = file.toPath();
} catch (final InvalidPathException e) {
/*
* Unlikely to happen. But if it happens anyway, try to open the channel in a
* way less surprising for the user (closer to the object he has specified).
*/
if (file.isFile()) {
return new Fallback(file, e);
}
}
}
}
/*
* One last check for URL. The URL may be either the given input if we have not been able
* to convert it to a Path, or a URI, File or CharSequence input converted to URL. Do not
* try to convert the URL to a Path, because this has already been tried before this point.
*/
if (storage instanceof URL) {
final URL file = (URL) storage;
return new ChannelFactory() {
@Override
public ReadableByteChannel readable(String filename, WarningListeners<DataStore> listeners) throws IOException {
return Channels.newChannel(file.openStream());
}
@Override
public WritableByteChannel writable(String filename, WarningListeners<DataStore> listeners) throws IOException {
return Channels.newChannel(file.openConnection().getOutputStream());
}
};
}
if (storage instanceof Path) {
final Path path = (Path) storage;
if (Files.isRegularFile(path)) {
return new ChannelFactory() {
@Override
public ReadableByteChannel readable(String filename, WarningListeners<DataStore> listeners) throws IOException {
return Files.newByteChannel(path, optionSet);
}
@Override
public WritableByteChannel writable(String filename, WarningListeners<DataStore> listeners) throws IOException {
return Files.newByteChannel(path, optionSet);
}
};
}
}
return null;
}
Aggregations