/*
* Copyright (c) 2007, 2009, 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 sun.nio.fs;
import java.nio.file.*;
import static java.nio.file.StandardOpenOption.*;
import java.nio.file.attribute.*;
import java.nio.channels.*;
import java.nio.ByteBuffer;
import java.io.*;
import java.util.*;
/**
* Base implementation class for a {@code Path}.
*/
abstract class AbstractPath extends Path {
protected AbstractPath() { }
@Override
public final Path createFile(FileAttribute<?>... attrs)
throws IOException
{
EnumSet<StandardOpenOption> options = EnumSet.of(CREATE_NEW, WRITE);
SeekableByteChannel sbc = newByteChannel(options, attrs);
try {
sbc.close();
} catch (IOException x) {
// ignore
}
return this;
}
/**
* Deletes a file. The {@code failIfNotExists} parameters determines if an
* {@code IOException} is thrown when the file does not exist.
*/
abstract void implDelete(boolean failIfNotExists) throws IOException;
@Override
public final void delete() throws IOException {
implDelete(true);
}
@Override
public final void deleteIfExists() throws IOException {
implDelete(false);
}
@Override
public final InputStream newInputStream(OpenOption... options)
throws IOException
{
if (options.length > 0) {
for (OpenOption opt: options) {
if (opt != READ)
throw new UnsupportedOperationException("'" + opt + "' not allowed");
}
}
return Channels.newInputStream(newByteChannel());
}
@Override
public final OutputStream newOutputStream(OpenOption... options)
throws IOException
{
int len = options.length;
Set<OpenOption> opts = new HashSet<OpenOption>(len + 3);
if (len == 0) {
opts.add(CREATE);
opts.add(TRUNCATE_EXISTING);
} else {
for (OpenOption opt: options) {
if (opt == READ)
throw new IllegalArgumentException("READ not allowed");
opts.add(opt);
}
}
opts.add(WRITE);
return Channels.newOutputStream(newByteChannel(opts));
}
@Override
public final SeekableByteChannel newByteChannel(OpenOption... options)
throws IOException
{
Set<OpenOption> set = new HashSet<OpenOption>(options.length);
Collections.addAll(set, options);
return newByteChannel(set);
}
private static final DirectoryStream.Filter<Path> acceptAllFilter =
new DirectoryStream.Filter<Path>() {
@Override public boolean accept(Path entry) { return true; }
};
@Override
public final DirectoryStream<Path> newDirectoryStream() throws IOException {
return newDirectoryStream(acceptAllFilter);
}
@Override
public final DirectoryStream<Path> newDirectoryStream(String glob)
throws IOException
{
// avoid creating a matcher if all entries are required.
if (glob.equals("*"))
return newDirectoryStream();
// create a matcher and return a filter that uses it.
final PathMatcher matcher = getFileSystem().getPathMatcher("glob:" + glob);
DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
@Override
public boolean accept(Path entry) {
return matcher.matches(entry.getName());
}
};
return newDirectoryStream(filter);
}
@Override
public final boolean exists() {
try {
checkAccess();
return true;
} catch (IOException x) {
// unable to determine if file exists
}
return false;
}
@Override
public final boolean notExists() {
try {
checkAccess();
return false;
} catch (NoSuchFileException x) {
// file confirmed not to exist
return true;
} catch (IOException x) {
return false;
}
}
private static final WatchEvent.Modifier[] NO_MODIFIERS = new WatchEvent.Modifier[0];
@Override
public final WatchKey register(WatchService watcher,
WatchEvent.Kind<?>... events)
throws IOException
{
return register(watcher, events, NO_MODIFIERS);
}
abstract void implCopyTo(Path target, CopyOption... options)
throws IOException;
@Override
public final Path copyTo(Path target, CopyOption... options)
throws IOException
{
if ((getFileSystem().provider() == target.getFileSystem().provider())) {
implCopyTo(target, options);
} else {
copyToForeignTarget(target, options);
}
return target;
}
abstract void implMoveTo(Path target, CopyOption... options)
throws IOException;
@Override
public final Path moveTo(Path target, CopyOption... options)
throws IOException
{
if ((getFileSystem().provider() == target.getFileSystem().provider())) {
implMoveTo(target, options);
} else {
// different providers so copy + delete
copyToForeignTarget(target, convertMoveToCopyOptions(options));
delete();
}
return target;
}
/**
* Converts the given array of options for moving a file to options suitable
* for copying the file when a move is implemented as copy + delete.
*/
private static CopyOption[] convertMoveToCopyOptions(CopyOption... options)
throws AtomicMoveNotSupportedException
{
int len = options.length;
CopyOption[] newOptions = new CopyOption[len+2];
for (int i=0; i<len; i++) {
CopyOption option = options[i];
if (option == StandardCopyOption.ATOMIC_MOVE) {
throw new AtomicMoveNotSupportedException(null, null,
"Atomic move between providers is not supported");
}
newOptions[i] = option;
}
newOptions[len] = LinkOption.NOFOLLOW_LINKS;
newOptions[len+1] = StandardCopyOption.COPY_ATTRIBUTES;
return newOptions;
}
/**
* Parses the arguments for a file copy operation.
*/
private static class CopyOptions {
boolean replaceExisting = false;
boolean copyAttributes = false;
boolean followLinks = true;
private CopyOptions() { }
static CopyOptions parse(CopyOption... options) {
CopyOptions result = new CopyOptions();
for (CopyOption option: options) {
if (option == StandardCopyOption.REPLACE_EXISTING) {
result.replaceExisting = true;
continue;
}
if (option == LinkOption.NOFOLLOW_LINKS) {
result.followLinks = false;
continue;
}
if (option == StandardCopyOption.COPY_ATTRIBUTES) {
result.copyAttributes = true;
continue;
}
if (option == null)
throw new NullPointerException();
throw new UnsupportedOperationException("'" + option +
"' is not a recognized copy option");
}
return result;
}
}
/**
* Simple cross-provider copy where the target is a Path.
*/
private void copyToForeignTarget(Path target, CopyOption... options)
throws IOException
{
CopyOptions opts = CopyOptions.parse(options);
LinkOption[] linkOptions = (opts.followLinks) ? new LinkOption[0] :
new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
// attributes of source file
BasicFileAttributes attrs = Attributes
.readBasicFileAttributes(this, linkOptions);
if (attrs.isSymbolicLink())
throw new IOException("Copying of symbolic links not supported");
// check if target exists
boolean exists;
if (opts.replaceExisting) {
try {
target.deleteIfExists();
exists = false;
} catch (DirectoryNotEmptyException x) {
// let exception translate to FileAlreadyExistsException (6895012)
exists = true;
}
} else {
exists = target.exists();
}
if (exists)
throw new FileAlreadyExistsException(target.toString());
// create directory or file
if (attrs.isDirectory()) {
target.createDirectory();
} else {
copyRegularFileToForeignTarget(target);
}
// copy basic attributes to target
if (opts.copyAttributes) {
BasicFileAttributeView view = target
.getFileAttributeView(BasicFileAttributeView.class, linkOptions);
try {
view.setTimes(attrs.lastModifiedTime(),
attrs.lastAccessTime(),
attrs.creationTime());
} catch (IOException x) {
// rollback
try {
target.delete();
} catch (IOException ignore) { }
throw x;
}
}
}
/**
* Simple copy of regular file to a target file that exists.
*/
private void copyRegularFileToForeignTarget(Path target)
throws IOException
{
ReadableByteChannel rbc = newByteChannel();
try {
// open target file for writing
SeekableByteChannel sbc = target.newByteChannel(CREATE_NEW, WRITE);
// simple copy loop
try {
ByteBuffer buf = ByteBuffer.wrap(new byte[8192]);
int n = 0;
for (;;) {
n = rbc.read(buf);
if (n < 0)
break;
assert n > 0;
buf.flip();
while (buf.hasRemaining()) {
sbc.write(buf);
}
buf.rewind();
}
} finally {
sbc.close();
}
} finally {
rbc.close();
}
}
/**
* Splits the given attribute name into the name of an attribute view and
* the attribute. If the attribute view is not identified then it assumed
* to be "basic".
*/
private static String[] split(String attribute) {
String[] s = new String[2];
int pos = attribute.indexOf(':');
if (pos == -1) {
s[0] = "basic";
s[1] = attribute;
} else {
s[0] = attribute.substring(0, pos++);
s[1] = (pos == attribute.length()) ? "" : attribute.substring(pos);
}
return s;
}
/**
* Gets a DynamicFileAttributeView by name. Returns {@code null} if the
* view is not available.
*/
abstract DynamicFileAttributeView getFileAttributeView(String name,
LinkOption... options);
@Override
public final void setAttribute(String attribute,
Object value,
LinkOption... options)
throws IOException
{
String[] s = split(attribute);
DynamicFileAttributeView view = getFileAttributeView(s[0], options);
if (view == null)
throw new UnsupportedOperationException("View '" + s[0] + "' not available");
view.setAttribute(s[1], value);
}
@Override
public final Object getAttribute(String attribute, LinkOption... options)
throws IOException
{
String[] s = split(attribute);
DynamicFileAttributeView view = getFileAttributeView(s[0], options);
return (view == null) ? null : view.getAttribute(s[1]);
}
@Override
public final Map<String,?> readAttributes(String attributes, LinkOption... options)
throws IOException
{
String[] s = split(attributes);
DynamicFileAttributeView view = getFileAttributeView(s[0], options);
if (view == null)
return Collections.emptyMap();
return view.readAttributes(s[1].split(","));
}
}