/**
* This file is protected by Copyright.
* Please refer to the COPYRIGHT file distributed with this source distribution.
*
* This file is part of REDHAWK IDE.
*
* All rights reserved. This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html.
*
*/
package gov.redhawk.core.filemanager.filesystem;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.omg.CORBA.Any;
import org.omg.CORBA.ORB;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAPackage.ObjectNotActive;
import org.omg.PortableServer.POAPackage.ServantAlreadyActive;
import org.omg.PortableServer.POAPackage.WrongPolicy;
import CF.DataType;
import CF.ErrorNumberType;
import CF.FileException;
import CF.FileHelper;
import CF.FileSystem;
import CF.InvalidFileName;
import CF.PropertiesHolder;
import CF.FileSystemPackage.FileInformationType;
import CF.FileSystemPackage.FileType;
import CF.FileSystemPackage.UnknownFileSystemProperties;
import gov.redhawk.core.filemanager.FileManagerPlugin;
/**
* A {@link CF.FileSystem} for the local file system (using <code>java.io.file</code>, <code>java.nio.file</code>).
*/
public class JavaFileSystem extends AbstractFileSystem {
private final File root;
public JavaFileSystem(final ORB orb, final POA poa, final File root) {
super(orb, poa);
this.root = root;
}
@Override
public void copy(final String sourceFileName, final String destinationFileName) throws InvalidFileName, FileException {
File sourceFile = precheck(sourceFileName);
File destinationFile = precheck(destinationFileName);
try {
if (!sourceFile.exists()) {
if (Files.notExists(sourceFile.toPath())) {
throw new FileException(ErrorNumberType.CF_ENOENT, "No such source file or directory");
} else {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
}
}
if (sourceFile.isDirectory()) {
throw new FileException(ErrorNumberType.CF_EISDIR, "Source is a directory");
}
Files.copy(sourceFile.toPath(), destinationFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (DirectoryNotEmptyException e) {
throw new FileException(ErrorNumberType.CF_ENOTEMPTY, "Target directory not empty");
} catch (AccessDeniedException e) {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
} catch (SecurityException e) {
throw new FileException(ErrorNumberType.CF_EACCES, e.getMessage());
} catch (final IOException e) {
throw new FileException(ErrorNumberType.CF_EIO, e.getMessage());
}
}
@Override
public void move(final String sourceFileName, final String destinationFileName) throws InvalidFileName, FileException {
File sourceFile = precheck(sourceFileName);
File destinationFile = precheck(destinationFileName);
try {
if (!sourceFile.exists()) {
if (Files.notExists(sourceFile.toPath())) {
throw new FileException(ErrorNumberType.CF_ENOENT, "No such source file or directory");
} else {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
}
}
Files.move(sourceFile.toPath(), destinationFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (DirectoryNotEmptyException e) {
throw new FileException(ErrorNumberType.CF_ENOTEMPTY, "Target directory not empty");
} catch (AccessDeniedException e) {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
} catch (SecurityException e) {
throw new FileException(ErrorNumberType.CF_EACCES, e.getMessage());
} catch (IOException e) {
throw new FileException(ErrorNumberType.CF_EIO, e.getMessage());
}
}
@Override
public CF.File create(final String fileName) throws InvalidFileName, FileException {
File file = precheck(fileName);
try {
Files.createFile(file.toPath());
final JavaFileFileImpl impl = new JavaFileFileImpl(file, false);
final byte[] id = this.poa.activate_object(impl);
return FileHelper.narrow(this.poa.id_to_reference(id));
} catch (AccessDeniedException e) {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
} catch (SecurityException e) {
throw new FileException(ErrorNumberType.CF_EACCES, e.getMessage());
} catch (FileAlreadyExistsException e) {
throw new FileException(ErrorNumberType.CF_EEXIST, "File already exists");
} catch (final IOException e) {
throw new FileException(ErrorNumberType.CF_EIO, e.getMessage());
} catch (final ServantAlreadyActive | WrongPolicy | ObjectNotActive e) {
throw new FileException(ErrorNumberType.CF_NOTSET, e.getMessage());
}
}
@Override
public boolean exists(final String fileName) throws InvalidFileName {
File file = precheck(fileName);
try {
return file.exists();
} catch (SecurityException e) {
throw new InvalidFileName(ErrorNumberType.CF_EACCES, e.getMessage());
}
}
@Override
public FileInformationType[] list(final String pattern) throws FileException, InvalidFileName {
if (pattern == null) {
throw new InvalidFileName(ErrorNumberType.CF_ENOENT, "No such file");
}
// Special case - an empty pattern is the way you get info for the root directory itself
if (pattern.trim().isEmpty()) {
return new FileInformationType[] { getFileInformationType(this.root.toPath()) };
}
IPath pathWithPattern = new Path(pattern).makeUNC(false);
if (!pathWithPattern.isAbsolute()) {
throw new InvalidFileName(ErrorNumberType.CF_EINVAL, "Path is not absolute");
}
// If they've asked for the root, or a path ending with a separator, give the contents of that directory
if (pathWithPattern.segmentCount() == 0 || pathWithPattern.hasTrailingSeparator()) {
pathWithPattern = pathWithPattern.append("*");
}
// Find files/dirs matching the pattern in the appropriate directory
final File parentPath = new File(this.root, pathWithPattern.removeLastSegments(1).toString());
final String filePatternOnly = escapePattern(pathWithPattern.lastSegment());
List<FileInformationType> fileInfos = new ArrayList<FileInformationType>();
try (DirectoryStream<java.nio.file.Path> directory = Files.newDirectoryStream(parentPath.toPath(), filePatternOnly)) {
for (java.nio.file.Path directoryEntry : directory) {
fileInfos.add(getFileInformationType(directoryEntry));
}
} catch (AccessDeniedException e) {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
} catch (SecurityException e) {
throw new FileException(ErrorNumberType.CF_EACCES, e.getMessage());
} catch (IOException e) {
throw new FileException(ErrorNumberType.CF_EIO, e.getMessage());
}
return fileInfos.toArray(new FileInformationType[fileInfos.size()]);
}
/**
* Escapes characters which are special file glob characters for
* {@link Files#newDirectoryStream(java.nio.file.Path, String)} but are normal characters for
* {@link FileSystem#list(String)}.
* @return
*/
private String escapePattern(String pattern) {
// Escape these characters: [ ] { }
return pattern.replaceAll("(\\[|\\]|\\{|\\})", "\\\\$1");
}
/**
* Gets a {@link CF.FileSystemPackage.FileInformationType} for the given path. If the target is unreadable, as
* much informatino as possible will be returned.
* @param file The file to return information for
* @return
*/
private FileInformationType getFileInformationType(java.nio.file.Path file) {
// Basic info
final FileInformationType fileInfo = new FileInformationType();
if (file.getNameCount() == 0) {
fileInfo.name = "/";
} else {
fileInfo.name = file.getFileName().toString();
}
// Get file attributes
BasicFileAttributes attrs = null;
try {
attrs = Files.readAttributes(file, BasicFileAttributes.class);
} catch (AccessDeniedException | NoSuchFileException e) {
// Try to read without following symlinks. If that works, the file is a symlink that points at something
// we either can't read (AccessDeniedException), or doesn't exist (NoSuchFileException). We'll try to
// report on the symlink itself.
try {
attrs = Files.readAttributes(file, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
} catch (IOException e2) {
// Just return basic information
fileInfo.kind = FileType.PLAIN;
fileInfo.size = 0;
return fileInfo;
}
} catch (IOException e) {
// Just return basic information
fileInfo.kind = FileType.PLAIN;
fileInfo.size = 0;
return fileInfo;
}
// Basic attributes
if (attrs.isDirectory()) {
fileInfo.kind = FileType.DIRECTORY;
} else {
fileInfo.kind = FileType.PLAIN;
}
fileInfo.size = attrs.size();
// Properties
List<DataType> properties = new ArrayList<DataType>();
long time = attrs.creationTime().to(TimeUnit.SECONDS);
if (time > 0) {
Any any = this.orb.create_any();
any.insert_ulonglong(time);
properties.add(new DataType("CREATED_TIME", any));
}
time = attrs.lastModifiedTime().to(TimeUnit.SECONDS);
if (time > 0) {
Any any = this.orb.create_any();
any.insert_ulonglong(time);
properties.add(new DataType("MODIFIED_TIME", any));
}
time = attrs.lastAccessTime().to(TimeUnit.SECONDS);
if (time > 0) {
Any any = this.orb.create_any();
any.insert_ulonglong(time);
properties.add(new DataType("LAST_ACCESS_TIME", any));
}
boolean writeable = Files.isWritable(file);
Any writeableAny = this.orb.create_any();
writeableAny.insert_boolean(!writeable);
properties.add(new DataType("READ_ONLY", writeableAny));
boolean executable = Files.isExecutable(file);
Any executableAny = this.orb.create_any();
executableAny.insert_boolean(executable);
properties.add(new DataType("EXECUTABLE", executableAny));
fileInfo.fileProperties = properties.toArray(new DataType[properties.size()]);
return fileInfo;
}
@Override
public void mkdir(final String directoryName) throws InvalidFileName, FileException {
File directory = precheck(directoryName);
try {
Files.createDirectory(directory.toPath());
} catch (FileAlreadyExistsException e) {
throw new FileException(ErrorNumberType.CF_EEXIST, "File exists");
} catch (AccessDeniedException e) {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
} catch (SecurityException e) {
throw new FileException(ErrorNumberType.CF_EACCES, e.getMessage());
} catch (IOException e) {
throw new FileException(ErrorNumberType.CF_NOTSET, e.getMessage());
}
new File(this.root, directoryName).mkdir();
}
@Override
public CF.File open(final String fileName, final boolean readOnly) throws InvalidFileName, FileException {
File file = precheck(fileName);
try {
if (!file.exists()) {
if (Files.notExists(file.toPath())) {
throw new FileException(ErrorNumberType.CF_ENOENT, "No such file");
} else {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
}
}
if (file.isDirectory()) {
throw new FileException(ErrorNumberType.CF_EISDIR, "Is a directory");
}
if (!file.canRead() || (!readOnly && !file.canWrite())) {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
}
final JavaFileFileImpl impl = new JavaFileFileImpl(file, readOnly);
final byte[] id = this.poa.activate_object(impl);
return FileHelper.narrow(this.poa.id_to_reference(id));
} catch (FileNotFoundException e) {
throw new FileException(ErrorNumberType.CF_ENOENT, "No such file");
} catch (SecurityException e) {
throw new FileException(ErrorNumberType.CF_EACCES, e.getMessage());
} catch (final ServantAlreadyActive | WrongPolicy | ObjectNotActive e) {
throw new FileException(ErrorNumberType.CF_NOTSET, e.getMessage());
}
}
@Override
public void query(final PropertiesHolder fileSystemProperties) throws UnknownFileSystemProperties {
if (fileSystemProperties.value.length == 0) {
fileSystemProperties.value = new DataType[] { new DataType("SIZE", null), new DataType("AVAILABLE_SPACE", null) };
}
final List<DataType> unknownProperties = new ArrayList<DataType>();
for (final DataType dataType : fileSystemProperties.value) {
if (dataType.id.equals("SIZE")) {
final Any any = this.orb.create_any();
try {
FileStore fileStore = Files.getFileStore(this.root.toPath());
any.insert_ulonglong(fileStore.getTotalSpace());
} catch (IOException e) {
FileManagerPlugin.logError("Unable to get capacity of root " + this.root.toString(), e);
any.insert_ulonglong(0);
}
dataType.value = any;
} else if (dataType.id.equals("AVAILABLE_SPACE")) {
final Any any = this.orb.create_any();
try {
FileStore fileStore = Files.getFileStore(this.root.toPath());
any.insert_ulonglong(fileStore.getUsableSpace());
} catch (final IOException e) {
FileManagerPlugin.logError("Unable to get available space of root " + this.root.toString(), e);
any.insert_ulonglong(0);
}
dataType.value = any;
} else {
unknownProperties.add(dataType);
}
}
if (unknownProperties.size() > 0) {
throw new UnknownFileSystemProperties(unknownProperties.toArray(new DataType[unknownProperties.size()]));
}
}
@Override
public void remove(final String fileName) throws FileException, InvalidFileName {
File file = precheck(fileName);
try {
if (!file.exists()) {
if (Files.notExists(file.toPath())) {
throw new FileException(ErrorNumberType.CF_ENOENT, "No such file");
} else {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
}
}
if (file.isDirectory()) {
throw new FileException(ErrorNumberType.CF_EISDIR, "Is a directory");
}
Files.delete(file.toPath());
} catch (AccessDeniedException e) {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
} catch (SecurityException e) {
throw new FileException(ErrorNumberType.CF_EACCES, e.getMessage());
} catch (IOException e) {
throw new FileException(ErrorNumberType.CF_NOTSET, e.getMessage());
}
}
@Override
public void rmdir(final String directoryName) throws InvalidFileName, FileException {
File directory = precheck(directoryName);
try {
if (!directory.exists()) {
if (Files.notExists(directory.toPath())) {
throw new FileException(ErrorNumberType.CF_ENOENT, "No such directory");
} else {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
}
}
if (!directory.isDirectory()) {
throw new FileException(ErrorNumberType.CF_ENOTDIR, "Not a directory");
}
Files.delete(directory.toPath());
} catch (DirectoryNotEmptyException e) {
throw new FileException(ErrorNumberType.CF_ENOTEMPTY, "Directory not empty");
} catch (AccessDeniedException e) {
throw new FileException(ErrorNumberType.CF_EACCES, "Permission denied");
} catch (SecurityException e) {
throw new FileException(ErrorNumberType.CF_EACCES, e.getMessage());
} catch (IOException e) {
throw new FileException(ErrorNumberType.CF_NOTSET, e.getMessage());
}
}
public void dispose() {
}
/**
* Checks that the path is non-empty and absolute. Returns a {@link File}.
* @param path The path to check
* @return A {@link java.io.File} object
* @throws InvalidFileName The path is null, empty, or not absolute
*/
private File precheck(String path) throws InvalidFileName {
if (path == null || path.trim().isEmpty()) {
throw new InvalidFileName(ErrorNumberType.CF_ENOENT, "No such file or directory");
}
File file = new File(path);
if (!file.isAbsolute()) {
throw new InvalidFileName(ErrorNumberType.CF_EINVAL, "Path is not absolute");
}
return new File(this.root, path);
}
}