1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-24 20:43:33 +08:00

no java nio

This commit is contained in:
赵怡然 2024-02-19 01:39:47 +08:00
parent 12533859ec
commit 723cd8458a
95 changed files with 5743 additions and 17 deletions
mirai-console/backend/mirai-console/src
MiraiConsole.ktMiraiConsoleImplementation.kt
com/llamalab/safs
AccessDeniedException.javaAtomicMoveNotSupportedException.javaClosedFileSystemException.javaClosedWatchServiceException.javaCopyOption.javaDirectoryIteratorException.javaDirectoryNotEmptyException.javaDirectoryStream.javaFileAlreadyExistsException.javaFileStore.javaFileSystem.javaFileSystemAlreadyExistsException.javaFileSystemException.javaFileSystemLoopException.javaFileSystemNotFoundException.javaFileSystems.javaFileVisitOption.javaFileVisitResult.javaFileVisitor.javaFiles.javaInvalidPathException.javaLinkOption.javaNoSuchFileException.javaNotDirectoryException.javaNotLinkException.javaOpenOption.javaPath.javaPathMatcher.javaPaths.javaProviderMismatchException.javaProviderNotFoundException.javaReadOnlyFileSystemException.javaSimpleFileVisitor.javaStandardCopyOption.javaStandardOpenOption.javaStandardWatchEventKinds.javaWatchEvent.javaWatchKey.javaWatchService.javaWatchable.java
attribute
channels
internal
java
kotlin/io/path
spi
unix
data
internal
plugin

View File

@ -34,7 +34,7 @@ import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.*
import java.io.File
import java.nio.file.Path
import com.llamalab.safs.Path
import java.time.Instant
/**

View File

@ -40,7 +40,7 @@ import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.*
import java.nio.file.Path
import com.llamalab.safs.Path
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.annotation.AnnotationTarget.*

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class AccessDeniedException extends FileSystemException {
public AccessDeniedException (String file) {
super(file);
}
public AccessDeniedException (String file, String otherFile, String reason) {
super(file, otherFile, reason);
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class AtomicMoveNotSupportedException extends FileSystemException {
public AtomicMoveNotSupportedException (String source, String target, String reason) {
super(source, target, reason);
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class ClosedFileSystemException extends IllegalStateException {
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class ClosedWatchServiceException extends IllegalStateException {}

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
public interface CopyOption {
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import java.io.IOException;
import java.util.ConcurrentModificationException;
@SuppressWarnings("serial")
public class DirectoryIteratorException extends ConcurrentModificationException {
public DirectoryIteratorException (IOException cause) {
initCause(cause);
}
@Override
public final IOException getCause () {
return (IOException)super.getCause();
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class DirectoryNotEmptyException extends FileSystemException {
public DirectoryNotEmptyException (String file) {
super(file);
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import java.io.Closeable;
import java.io.IOException;
public interface DirectoryStream<T> extends Iterable<T>, Closeable {
public interface Filter<T> {
public boolean accept (T entry) throws IOException;
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class FileAlreadyExistsException extends FileSystemException {
public FileAlreadyExistsException (String file) {
super(file);
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import java.io.IOException;
public abstract class FileStore {
protected FileStore () {}
public abstract String name ();
public abstract String type ();
public abstract boolean isReadOnly ();
public abstract long getTotalSpace () throws IOException;
public abstract long getUsableSpace () throws IOException;
public abstract long getUnallocatedSpace () throws IOException;
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import com.llamalab.safs.attribute.UserPrincipalLookupService;
import com.llamalab.safs.spi.FileSystemProvider;
import java.io.Closeable;
import java.io.IOException;
import java.util.Set;
public abstract class FileSystem implements Closeable {
public abstract FileSystemProvider provider ();
public abstract boolean isOpen ();
public abstract boolean isReadOnly ();
public abstract Set<String> supportedFileAttributeViews();
public abstract String getSeparator ();
public abstract Path getPath (String first, String... more);
public abstract PathMatcher getPathMatcher (String syntaxAndPattern);
public abstract Iterable<FileStore> getFileStores ();
public abstract Iterable<Path> getRootDirectories ();
public abstract UserPrincipalLookupService getUserPrincipalLookupService ();
public abstract WatchService newWatchService () throws IOException;
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class FileSystemAlreadyExistsException extends RuntimeException {
public FileSystemAlreadyExistsException () {}
public FileSystemAlreadyExistsException (String message) {
super(message);
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import java.io.IOException;
@SuppressWarnings("serial")
public class FileSystemException extends IOException {
private final String file;
private final String otherFile;
public FileSystemException (String file) {
this(file, null, null);
}
public FileSystemException (String file, String otherFile, String reason) {
super(reason);
this.file = file;
this.otherFile = otherFile;
}
public String getFile () {
return file;
}
public String getOtherFile () {
return otherFile;
}
public String getReason () {
return super.getMessage();
}
@Override
public String getMessage() {
if (file == null && otherFile == null)
return getReason();
final StringBuilder sb = new StringBuilder();
if (file != null)
sb.append(file);
if (otherFile != null)
sb.append(" -> ").append(otherFile);
if (getReason() != null)
sb.append(": ").append(getReason());
return sb.toString();
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class FileSystemLoopException extends FileSystemException {
public FileSystemLoopException (String file) {
super(file);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class FileSystemNotFoundException extends RuntimeException {
public FileSystemNotFoundException () {}
public FileSystemNotFoundException (String message) {
super(message);
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import com.llamalab.safs.java.DefaultJavaFileSystemProvider;
import com.llamalab.safs.spi.FileSystemProvider;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
public final class FileSystems {
private FileSystems () {}
public static FileSystem getDefault () {
return DefaultFileSystemHolder.fileSystem;
}
public static FileSystem getFileSystem (URI uri) {
for (final FileSystemProvider provider : FileSystemProvider.installedProviders()) {
if (provider.getScheme().equalsIgnoreCase(uri.getScheme()))
return provider.getFileSystem(uri);
}
throw new ProviderNotFoundException(uri.getScheme());
}
public static FileSystem newFileSystem (URI uri, Map<String,?> env) throws IOException {
for (final FileSystemProvider provider : FileSystemProvider.installedProviders()) {
if (provider.getScheme().equalsIgnoreCase(uri.getScheme()))
return provider.newFileSystem(uri, env);
}
throw new ProviderNotFoundException(uri.getScheme());
}
private static final class DefaultFileSystemHolder {
static final FileSystem fileSystem = loadDefaultProvider().getFileSystem(URI.create("file:///"));
private static FileSystemProvider loadDefaultProvider () {
FileSystemProvider provider = new DefaultJavaFileSystemProvider();
final String value = System.getProperty("com.llamalab.safs.spi.DefaultFileSystemProvider");
if (value != null) {
try {
for (final String className : value.split(",")) {
provider = (FileSystemProvider)Class.forName(className)
.getDeclaredConstructor(FileSystemProvider.class)
.newInstance(provider);
}
}
catch (Throwable t) {
throw new Error(t);
}
}
return provider;
}
} // class DefaultFileSystemHolder
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
public enum FileVisitOption {
/** Follow symbolic links. */
FOLLOW_LINKS,
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
public enum FileVisitResult {
CONTINUE,
TERMINATE,
SKIP_SUBTREE,
SKIP_SIBLINGS,
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import com.llamalab.safs.attribute.BasicFileAttributes;
import java.io.IOException;
public interface FileVisitor<T> {
public FileVisitResult preVisitDirectory (T dir, BasicFileAttributes attrs) throws IOException;
public FileVisitResult postVisitDirectory (T dir, IOException e) throws IOException;
public FileVisitResult visitFile (T file, BasicFileAttributes attrs) throws IOException;
public FileVisitResult visitFileFailed (T file, IOException e) throws IOException;
}

View File

@ -0,0 +1,630 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import com.llamalab.safs.attribute.BasicFileAttributes;
import com.llamalab.safs.attribute.FileAttribute;
import com.llamalab.safs.attribute.FileAttributeView;
import com.llamalab.safs.attribute.FileTime;
import com.llamalab.safs.internal.BasicFileAttribute;
import com.llamalab.safs.internal.DefaultFileSystem;
import com.llamalab.safs.internal.SearchSet;
import com.llamalab.safs.internal.Utils;
import com.llamalab.safs.channels.SeekableByteChannel;
import com.llamalab.safs.spi.FileSystemProvider;
import com.llamalab.safs.spi.FileTypeDetector;
import org.apache.commons.lang3.NotImplementedException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
public final class Files {
//private static final OpenOption[] COPY_REPLACE_EXISTING = { StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE };
//private static final OpenOption[] COPY_KEEP_EXISTING = { StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW };
private static final LinkOption[] LINK_NOFOLLOW_LINKS = { LinkOption.NOFOLLOW_LINKS };
private static final Set<FileVisitOption> VISIT_EMPTY = EnumSet.noneOf(FileVisitOption.class);
private static final String COPIED_ATTRIBUTES = BasicFileAttribute.lastModifiedTime + "," + BasicFileAttribute.lastAccessTime + "," + BasicFileAttribute.creationTime;
private static final SecureRandom TEMP_RAND = new SecureRandom();
private Files () {}
public static FileStore getFileStore (Path path) throws IOException {
return provider(path).getFileStore(path);
}
public static void delete (Path path) throws IOException {
provider(path).delete(path);
}
private static void deleteQuietly (Path path) {
try {
delete(path);
}
catch (Throwable t) {
// ignore
}
}
public static boolean deleteIfExists (Path path) throws IOException {
return provider(path).deleteIfExists(path);
}
public static Path createDirectory (Path dir, FileAttribute<?>... attrs) throws IOException {
provider(dir).createDirectory(dir, attrs);
return dir;
}
public static Path createDirectories (Path dir, FileAttribute<?>... attrs) throws IOException {
final FileSystemProvider provider = provider(dir);
try {
provider.createDirectory(dir, attrs);
return dir;
}
catch (FileAlreadyExistsException e) {
if (Files.isDirectory(dir))
return dir;
throw e;
}
catch (Exception e) {
// ignore
}
final Path absolute = dir.toAbsolutePath();
Path parent = absolute.getParent();
for (;; parent = parent.getParent()) {
if (parent == null)
throw new FileSystemException(dir.toString(), null, "No root directory");
try {
final BasicFileAttributes basic = provider.readAttributes(parent, BasicFileAttributes.class);
if (!basic.isDirectory())
throw new FileAlreadyExistsException(dir.toString());
break;
}
catch (NoSuchFileException e) {
// continue
}
}
Path child = parent;
for (final Path name : parent.relativize(absolute))
provider.createDirectory(child = child.resolve(name));
return dir;
}
public static Path createFile (Path path, FileAttribute<?>... attrs) throws IOException {
try {
newByteChannel(path, EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW), attrs).close();
}
catch (UnsupportedOperationException e) {
if (attrs.length != 0)
throw e;
newOutputStream(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW).close();
}
return path;
}
private static Path makeTempPath (Path dir, String prefix, String suffix) {
long n = TEMP_RAND.nextLong();
if (n == Long.MIN_VALUE)
n = 0;
else if (n < 0)
n *= -1;
final Path name = dir.getFileSystem().getPath(prefix + Long.toString(n) + suffix);
if (name.getParent() != null)
throw new IllegalArgumentException("Invalid prefix or suffix");
return dir.resolve(name);
}
public static Path createTempFile (Path dir, String prefix, String suffix, FileAttribute<?>... attrs) throws IOException {
if (prefix == null)
prefix = "";
if (suffix == null)
suffix = ".tmp";
for (;;) {
try {
return createFile(makeTempPath(dir, prefix, suffix), attrs);
}
catch (FileAlreadyExistsException e) {
// retry
}
}
}
public static Path createTempFile (String prefix, String suffix, FileAttribute<?>... attrs) throws IOException {
final FileSystem fs = FileSystems.getDefault();
if (!(fs instanceof DefaultFileSystem))
throw new UnsupportedOperationException();
return createTempFile(((DefaultFileSystem)fs).getCacheDirectory(), prefix, suffix, attrs);
}
public static Path createTempDirectory (Path dir, String prefix, FileAttribute<?>... attrs) throws IOException {
if (prefix == null)
prefix = "";
for (;;) {
try {
return Files.createDirectory(makeTempPath(dir, prefix, ""), attrs);
}
catch (FileAlreadyExistsException e) {
// retry
}
}
}
public static Path createTempDirectory (String prefix, FileAttribute<?>... attrs) throws IOException {
final FileSystem fs = FileSystems.getDefault();
if (!(fs instanceof DefaultFileSystem))
throw new UnsupportedOperationException();
return createTempDirectory(((DefaultFileSystem)fs).getCacheDirectory(), prefix, attrs);
}
public static byte[] readAllBytes (Path path) throws IOException {
final InputStream in = newInputStream(path);
try {
final long size = size(path);
if (size > Integer.MAX_VALUE)
throw new OutOfMemoryError("Array size exceeded");
return Utils.readAllBytes(in, (int)size);
}
finally {
in.close();
}
}
public static Path write (Path path, byte[] bytes, OpenOption... options) throws IOException {
final OutputStream out = newOutputStream(path, options);
try {
out.write(bytes);
}
finally {
out.close();
}
return path;
}
/**
* Does not delete target upon failure.
*/
public static long copy (InputStream in, Path target, CopyOption... options) throws IOException {
if (in == null)
throw new NullPointerException("in");
for (final CopyOption option : options) {
if (StandardCopyOption.REPLACE_EXISTING == option) {
deleteIfExists(target);
break;
}
}
final OutputStream out = newOutputStream(target, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
try {
return Utils.transfer(in, out);
}
finally {
out.close();
}
}
public static long copy (Path source, OutputStream out) throws IOException {
final InputStream in = newInputStream(source);
try {
return Utils.transfer(in, out);
}
finally {
out.close();
}
}
public static Path copy (Path source, Path target, CopyOption... options) throws IOException {
return transfer(source, target, false, options);
}
public static Path move (Path source, Path target, CopyOption... options) throws IOException {
return transfer(source, target, true, options);
}
private static Path transfer (Path source, Path target, boolean move, CopyOption[] options) throws IOException {
final FileSystemProvider provider = provider(source);
if (provider == provider(target)) {
if (move)
provider.move(source, target, options);
else
provider.copy(source, target, options);
return target;
}
boolean replaceExisting = false;
boolean copyAttributes = false;
LinkOption[] linkOptions = Utils.EMPTY_LINK_OPTION_ARRAY;
for (final CopyOption option : options) {
if (StandardCopyOption.REPLACE_EXISTING == option)
replaceExisting = true;
else if (StandardCopyOption.COPY_ATTRIBUTES == option)
copyAttributes = true;
else if (LinkOption.NOFOLLOW_LINKS == option)
linkOptions = LINK_NOFOLLOW_LINKS;
else if (StandardCopyOption.ATOMIC_MOVE == option && move)
throw new AtomicMoveNotSupportedException(source.toString(), target.toString(), "Different providers");
}
if (replaceExisting)
deleteIfExists(target);
if (isDirectory(source))
createDirectory(target);
else {
final InputStream in = newInputStream(source);
try {
final OutputStream out = newOutputStream(target, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
try {
Utils.transfer(in, out);
}
finally {
out.close();
}
}
finally {
in.close();
}
}
try {
if (copyAttributes) {
for (final Map.Entry<String, Object> attr : readAttributes(source, COPIED_ATTRIBUTES, linkOptions).entrySet()) {
try {
setAttribute(target, attr.getKey(), attr.getValue());
}
catch (UnsupportedOperationException e) {
// suppressed
}
}
}
if (move)
delete(source);
}
catch (IOException e) {
deleteQuietly(target);
throw e;
}
catch (RuntimeException e) {
deleteQuietly(target);
throw e;
}
return target;
}
public static SeekableByteChannel newByteChannel(Path path, OpenOption... options) throws IOException {
return provider(path).newByteChannel(path, new SearchSet<OpenOption>(options));
}
public static SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
return provider(path).newByteChannel(path, options, attrs);
}
public static InputStream newInputStream (Path path, OpenOption...options) throws IOException {
return provider(path).newInputStream(path);
}
public static OutputStream newOutputStream (Path path, OpenOption...options) throws IOException {
return provider(path).newOutputStream(path, options);
}
public static BufferedReader newBufferedReader (Path path) throws IOException {
return newBufferedReader(path, Utils.UTF_8);
}
public static BufferedReader newBufferedReader (Path path, Charset charset) throws IOException {
return new BufferedReader(new InputStreamReader(newInputStream(path), charset));
}
public static BufferedWriter newBufferedWriter (Path path, OpenOption... options) throws IOException {
return newBufferedWriter(path, Utils.UTF_8, options);
}
public static BufferedWriter newBufferedWriter (Path path, Charset charset, OpenOption... options) throws IOException {
return new BufferedWriter(new OutputStreamWriter(newOutputStream(path, options), charset));
}
public static boolean exists (Path path, LinkOption... options) {
try {
readAttributes(path, BasicFileAttributes.class, options);
return true;
}
catch (IOException e) {
return false;
}
}
public static boolean notExists (Path path, LinkOption... options) {
try {
readAttributes(path, BasicFileAttributes.class, options);
return false;
}
catch (NoSuchFileException e) {
return true;
}
catch (IOException e) {
return false;
}
}
public static boolean isSameFile (Path path1, Path path2) throws IOException {
final FileSystem fs = path1.getFileSystem();
return fs.equals(path2.getFileSystem())
&& fs.provider().isSameFile(path1, path2);
}
public static boolean isHidden (Path path) throws IOException {
return provider(path).isHidden(path);
}
public static boolean isDirectory (Path path, LinkOption... options) {
try {
return readAttributes(path, BasicFileAttributes.class, options).isDirectory();
}
catch (IOException e) {
return false;
}
}
public static boolean isRegularFile (Path path, LinkOption... options) {
try {
return readAttributes(path, BasicFileAttributes.class, options).isRegularFile();
}
catch (IOException e) {
return false;
}
}
public static boolean isSymbolicLink (Path path) {
try {
readSymbolicLink(path);
return true;
}
catch (IOException e) {
return false;
}
}
public static long size (Path path) throws IOException {
return readAttributes(path, BasicFileAttributes.class).size();
}
public static FileTime getLastModifiedTime (Path path, LinkOption... options) throws IOException {
return readAttributes(path, BasicFileAttributes.class, options).lastModifiedTime();
}
public static Path setLastModifiedTime (Path path, FileTime time) throws IOException {
return setAttribute(path, BasicFileAttribute.lastModifiedTime.toString(), time);
}
public static Path readSymbolicLink (Path link) throws IOException {
return provider(link).readSymbolicLink(link);
}
public static <A extends BasicFileAttributes> A readAttributes (Path path, Class<A> type, LinkOption... options) throws IOException {
return provider(path).readAttributes(path, type, options);
}
public static Map<String,Object> readAttributes (Path path, String attributes, LinkOption... options) throws IOException {
return provider(path).readAttributes(path, attributes, options);
}
public static Path setAttribute (Path path, String attribute, Object value, LinkOption... options) throws IOException {
provider(path).setAttribute(path, attribute, value, options);
return path;
}
public static <V extends FileAttributeView> V getFileAttributeView (Path path, Class<V> type, LinkOption... options) {
return provider(path).getFileAttributeView(path, type, options);
}
public static String probeContentType (Path path) throws IOException {
for (final FileTypeDetector detector : InstalledFileTypeDetectorsHolder.detectors) {
final String type = detector.probeContentType(path);
if (type != null)
return type;
}
return null;
}
public static DirectoryStream<Path> newDirectoryStream (Path dir) throws IOException{
return newDirectoryStream(dir, Utils.ACCEPT_ALL_FILTER);
}
public static DirectoryStream<Path> newDirectoryStream (Path dir, String glob) throws IOException {
if ("*".equals(glob))
return newDirectoryStream(dir);
final PathMatcher matcher = dir.getFileSystem().getPathMatcher("glob:"+glob);
//noinspection RedundantThrows
return newDirectoryStream(dir, new DirectoryStream.Filter<Path>() {
@Override
public boolean accept (Path entry) throws IOException {
return matcher.matches(entry.getFileName());
}
});
}
public static DirectoryStream<Path> newDirectoryStream (Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException{
return provider(dir).newDirectoryStream(dir, filter);
}
public static Path walkFileTree (Path start, FileVisitor<? super Path> visitor) throws IOException {
return walkFileTree(start, VISIT_EMPTY, Integer.MAX_VALUE, visitor);
}
@SuppressWarnings("ConstantConditions")
public static Path walkFileTree (Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor) throws IOException {
final boolean followLinks = options.contains(FileVisitOption.FOLLOW_LINKS);
final LinkOption[] linkOptions = followLinks ? Utils.EMPTY_LINK_OPTION_ARRAY : LINK_NOFOLLOW_LINKS;
WalkDirectory dir = null;
try {
Path path = start;
FileVisitResult result;
int depth = 0;
walk: do {
// iterate
while (dir != null) {
IOException cause = null;
try {
if (dir.iterator.hasNext()) {
path = dir.iterator.next();
break;
}
}
catch (DirectoryIteratorException e) {
cause = e.getCause();
}
if (FileVisitResult.TERMINATE == visitor.postVisitDirectory(dir.path, cause) || --depth <= 0)
break walk;
Utils.closeQuietly(dir.stream);
dir = dir.parent;
}
// descend
DirectoryStream<Path> stream = null;
BasicFileAttributes attrs = null;
try {
attrs = readAttributes(path, BasicFileAttributes.class, linkOptions);
if (attrs.isDirectory() && depth < maxDepth) {
if (followLinks) {
// check for recursion
for (WalkDirectory ancestor = dir; ancestor != null; ancestor = ancestor.parent) {
if (ancestor.isSameFile(path, attrs.fileKey()))
throw new FileSystemLoopException(path.toString());
}
}
stream = Files.newDirectoryStream(path);
}
}
catch (NotDirectoryException e) {
// visitFile below
}
catch (IOException e) {
result = visitor.visitFileFailed(path, e);
if (FileVisitResult.SKIP_SIBLINGS == result && dir != null)
dir.iterator = Utils.emptyIterator();
continue;
}
if (stream != null) {
result = visitor.preVisitDirectory(path, attrs);
if (FileVisitResult.CONTINUE == result) {
dir = new WalkDirectory(dir, path, attrs.fileKey(), stream);
++depth;
}
else
Utils.closeQuietly(stream);
}
else {
result = visitor.visitFile(path, attrs);
if (FileVisitResult.SKIP_SIBLINGS == result && dir != null)
dir.iterator = Utils.emptyIterator();
}
} while (FileVisitResult.TERMINATE != result && depth > 0);
}
finally {
for (; dir != null; dir = dir.parent)
Utils.closeQuietly(dir.stream);
}
return start;
}
public static File[] walk(@Nullable Path path) {
throw new NotImplementedException("我懒得写");
}
private static final class WalkDirectory {
public final WalkDirectory parent;
public final Path path;
public final Object key;
public final DirectoryStream<Path> stream;
public Iterator<Path> iterator;
public WalkDirectory (WalkDirectory parent, Path path, Object key, DirectoryStream<Path> stream) {
this.parent = parent;
this.path = path;
this.key = key;
this.stream = stream;
this.iterator = stream.iterator();
}
public boolean isSameFile (Path path, Object key) {
if (key != null && this.key != null)
return key.equals(this.key);
try {
return Files.isSameFile(path, this.path);
}
catch (IOException e) {
return false;
}
}
} // class WalkDirectory
private static FileSystemProvider provider (Path path) {
return path.getFileSystem().provider();
}
private static final class InstalledFileTypeDetectorsHolder {
private static final List<FileTypeDetector> detectors = loadInstalledProviders();
private static List<FileTypeDetector> loadInstalledProviders () {
final List<FileTypeDetector> detectors = new ArrayList<FileTypeDetector>();
for (final FileTypeDetector detector : ServiceLoader.load(FileTypeDetector.class, FileTypeDetector.class.getClassLoader()))
detectors.add(detector);
//noinspection RedundantThrows
detectors.add(new FileTypeDetector() {
private final boolean apacheHarmony = isApacheHarmony();
@Override
public String probeContentType (Path path) throws IOException {
// TODO: Use URLConnection.guessContentTypeFromStream()
String filename = path.toString();
// BUG: https://code.google.com/p/android/issues/detail?id=162883
// https://android.googlesource.com/platform/libcore/+/marshmallow-release/luni/src/main/java/java/net/DefaultFileNameMap.java
if (apacheHarmony)
filename = filename.replace('#', '_');
return URLConnection.guessContentTypeFromName(filename);
}
});
return detectors;
}
private static boolean isApacheHarmony () {
try {
Class.forName("libcore.net.MimeUtils", false, InstalledFileTypeDetectorsHolder.class.getClassLoader());
return true;
}
catch (ClassNotFoundException e) {
return false;
}
}
} // class InstalledFileTypeDetectorsHolder
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class InvalidPathException extends IllegalArgumentException {
private final String input;
private final int index;
public InvalidPathException (String input, String reason) {
this(input, reason, -1);
}
public InvalidPathException (String input, String reason, int index) {
super(reason);
this.input = input;
this.index = index;
}
public String getInput () {
return input;
}
public String getReason () {
return super.getMessage();
}
public int getIndex () {
return index;
}
@Override
public String getMessage () {
final StringBuilder sb = new StringBuilder().append(getReason());
if (index >= 0)
sb.append(" at index ").append(index);
return sb.append(": ").append(input).toString();
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
public enum LinkOption implements OpenOption, CopyOption {
/** Do not follow symbolic links. */
NOFOLLOW_LINKS,
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class NoSuchFileException extends FileSystemException {
public NoSuchFileException (String file) {
super(file);
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class NotDirectoryException extends FileSystemException {
public NotDirectoryException (String file) {
super(file);
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class NotLinkException extends FileSystemException {
public NotLinkException (String file) {
super(file);
}
public NotLinkException (String file, String otherFile, String reason) {
super(file, otherFile, reason);
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
public interface OpenOption {}

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import java.io.File;
import java.io.IOException;
import java.net.URI;
public interface Path extends Comparable<Path>, Iterable<Path>, Watchable {
public FileSystem getFileSystem ();
public boolean isAbsolute ();
public Path getRoot ();
public Path getParent ();
public Path getFileName ();
public int getNameCount ();
public Path getName (int index);
public Path subpath (int beginIndex, int endIndex);
public boolean startsWith (Path other);
public boolean startsWith (String other);
public boolean endsWith (Path other);
public boolean endsWith (String other);
public Path normalize ();
public Path relativize (Path other);
public Path resolve (Path other);
public Path resolve (String other);
public Path resolveSibling (Path other);
public Path resolveSibling (String other);
public Path toAbsolutePath ();
public Path toRealPath (LinkOption... options) throws IOException;
public File toFile ();
public URI toUri ();
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
public interface PathMatcher {
public boolean matches (Path path);
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import com.llamalab.safs.spi.FileSystemProvider;
import java.io.File;
import java.net.URI;
public final class Paths {
private Paths () {}
public static Path get (String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
public static Path get (URI uri) {
final String scheme = uri.getScheme();
if (scheme == null)
throw new IllegalArgumentException("No scheme");
for (final FileSystemProvider provider: FileSystemProvider.installedProviders()) {
if (scheme.equalsIgnoreCase(provider.getScheme()))
return provider.getPath(uri);
}
throw new FileSystemNotFoundException(scheme);
}
/**
* Non-standard. Since we can't add toPath to {@link java.io.File}.
*/
public static Path get (File file) {
return get(file.toString());
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class ProviderMismatchException extends IllegalArgumentException {
public ProviderMismatchException () {}
public ProviderMismatchException (String message) {
super(message);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class ProviderNotFoundException extends RuntimeException {
public ProviderNotFoundException () {}
public ProviderNotFoundException (String message) {
super(message);
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
@SuppressWarnings("serial")
public class ReadOnlyFileSystemException extends UnsupportedOperationException {
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import com.llamalab.safs.attribute.BasicFileAttributes;
import java.io.IOException;
public class SimpleFileVisitor<T> implements FileVisitor<T> {
@Override
public FileVisitResult preVisitDirectory (T dir, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory (T dir, IOException e) throws IOException {
if (e != null)
throw e;
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile (T file, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed (T file, IOException e) throws IOException {
throw e;
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
public enum StandardCopyOption implements CopyOption {
REPLACE_EXISTING,
COPY_ATTRIBUTES,
ATOMIC_MOVE,
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
public enum StandardOpenOption implements OpenOption {
READ,
WRITE,
APPEND,
TRUNCATE_EXISTING,
CREATE,
CREATE_NEW,
/** Unsupported. */
DELETE_ON_CLOSE,
SPARSE,
SYNC,
DSYNC,
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import com.llamalab.safs.internal.WatchEventKind;
public final class StandardWatchEventKinds {
public static final WatchEvent.Kind<Object> OVERFLOW = new WatchEventKind<Object>("OVERFLOW", Object.class);
public static final WatchEvent.Kind<Path> ENTRY_CREATE = new WatchEventKind<Path>("ENTRY_CREATE", Path.class);
public static final WatchEvent.Kind<Path> ENTRY_DELETE = new WatchEventKind<Path>("ENTRY_DELETE", Path.class);
public static final WatchEvent.Kind<Path> ENTRY_MODIFY = new WatchEventKind<Path>("ENTRY_MODIFY", Path.class);
private StandardWatchEventKinds() {}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
public interface WatchEvent<T> {
public static interface Kind<T> {
public String name ();
public Class<T> type ();
}
public static interface Modifier {
public String name ();
}
public Kind<T> kind ();
public T context ();
public int count ();
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import java.util.List;
public interface WatchKey {
public Watchable watchable ();
public boolean isValid ();
public boolean reset ();
public void cancel ();
public List<WatchEvent<?>> pollEvents ();
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import java.io.Closeable;
import java.util.concurrent.TimeUnit;
public interface WatchService extends Closeable {
public WatchKey poll ();
public WatchKey poll (long timeout, TimeUnit unit) throws InterruptedException;
public WatchKey take () throws InterruptedException;
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs;
import java.io.IOException;
public interface Watchable {
public WatchKey register (WatchService service, WatchEvent.Kind<?>[] kinds, WatchEvent.Modifier... modifiers) throws IOException;
public WatchKey register (WatchService service, WatchEvent.Kind<?>... kinds) throws IOException;
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
public interface AttributeView {
public String name ();
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
import java.io.IOException;
public interface BasicFileAttributeView extends FileAttributeView {
public BasicFileAttributes readAttributes () throws IOException;
public void setTimes (FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException;
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
public interface BasicFileAttributes {
public Object fileKey ();
public boolean isDirectory ();
public boolean isOther ();
public boolean isRegularFile ();
public boolean isSymbolicLink ();
public long size ();
public FileTime creationTime ();
public FileTime lastModifiedTime ();
public FileTime lastAccessTime ();
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
public interface FileAttribute<T> {
public String name ();
public T value ();
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
public interface FileAttributeView extends AttributeView {
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
import java.io.IOException;
public interface FileOwnerAttributeView extends FileAttributeView {
public UserPrincipal getOwner () throws IOException;
public void setOwner (UserPrincipal owner) throws IOException;
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
import com.llamalab.safs.internal.Utils;
import java.util.concurrent.TimeUnit;
public final class FileTime implements Comparable<FileTime> {
private static final FileTime ZERO = new FileTime(0);
private final long value;
private FileTime (long value) {
this.value = value;
}
/**
* YYYY-MM-DDThh:mm:ss[.s+]Z
*/
@Override
public String toString () {
return Utils.formatRfc3339(value);
}
@Override
public int hashCode () {
return (int)(value ^ (value >>> 32));
}
@Override
public boolean equals (Object obj) {
if (this == obj)
return true;
if (!(obj instanceof FileTime))
return false;
return value == ((FileTime)obj).value;
}
@Override
public int compareTo (FileTime other) {
final long lhs = this.value;
final long rhs = other.value;
//noinspection UseCompareMethod
return (lhs < rhs) ? -1 : (lhs == rhs) ? 0 : 1;
}
public long to (TimeUnit unit) {
return unit.convert(value, TimeUnit.MILLISECONDS);
}
public long toMillis () {
return to(TimeUnit.MILLISECONDS);
}
public static FileTime from (long value, TimeUnit unit) {
if (unit == null)
throw new NullPointerException("unit");
return fromMillis(TimeUnit.MILLISECONDS.convert(value, unit));
}
public static FileTime fromMillis (long value) {
return (value != 0) ? new FileTime(value) : ZERO;
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
public interface GroupPrincipal extends UserPrincipal {
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
import java.io.IOException;
import java.util.Set;
public interface PosixFileAttributeView extends BasicFileAttributeView, FileOwnerAttributeView {
public void setGroup (GroupPrincipal group) throws IOException;
public void setPermissions (Set<PosixFilePermission> perms) throws IOException;
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
import java.util.Set;
public interface PosixFileAttributes extends BasicFileAttributes {
public GroupPrincipal group ();
public UserPrincipal owner ();
public Set<PosixFilePermission> permissions ();
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
public enum PosixFilePermission {
OWNER_READ,
OWNER_WRITE,
OWNER_EXECUTE,
GROUP_READ,
GROUP_WRITE,
GROUP_EXECUTE,
OTHERS_READ,
OTHERS_WRITE,
OTHERS_EXECUTE
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
import com.llamalab.safs.internal.SearchSet;
import java.util.EnumSet;
import java.util.Set;
public final class PosixFilePermissions {
private PosixFilePermissions () {}
public static String toString (Set<PosixFilePermission> perms) {
final StringBuilder sb = new StringBuilder(9);
append(sb,
perms.contains(PosixFilePermission.OWNER_READ),
perms.contains(PosixFilePermission.OWNER_WRITE),
perms.contains(PosixFilePermission.OWNER_EXECUTE));
append(sb,
perms.contains(PosixFilePermission.GROUP_READ),
perms.contains(PosixFilePermission.GROUP_WRITE),
perms.contains(PosixFilePermission.GROUP_EXECUTE));
append(sb,
perms.contains(PosixFilePermission.OTHERS_READ),
perms.contains(PosixFilePermission.OTHERS_WRITE),
perms.contains(PosixFilePermission.OTHERS_EXECUTE));
return sb.toString();
}
private static void append (StringBuilder sb, boolean r, boolean w, boolean x) {
sb.append(r ? 'r' : '-').append(w ? 'w' : '-').append(x ? 'x' : '-');
}
public static Set<PosixFilePermission> fromString (String mode) {
if (mode.length() != 9)
throw new IllegalArgumentException();
final Set<PosixFilePermission> set = EnumSet.noneOf(PosixFilePermission.class);
add(set, mode, 0, 'r', PosixFilePermission.OWNER_READ);
add(set, mode, 1, 'w', PosixFilePermission.OWNER_WRITE);
add(set, mode, 2, 'x', PosixFilePermission.OWNER_EXECUTE);
add(set, mode, 3, 'r', PosixFilePermission.GROUP_READ);
add(set, mode, 4, 'w', PosixFilePermission.GROUP_WRITE);
add(set, mode, 5, 'x', PosixFilePermission.GROUP_EXECUTE);
add(set, mode, 6, 'r', PosixFilePermission.OTHERS_READ);
add(set, mode, 7, 'w', PosixFilePermission.OTHERS_WRITE);
add(set, mode, 8, 'x', PosixFilePermission.OTHERS_EXECUTE);
return set;
}
private static void add (Set<PosixFilePermission> set, String mode, int index, char flag, PosixFilePermission perm) {
final char c = mode.charAt(index);
if (flag == c)
set.add(perm);
else if ('-' != c)
throw new IllegalArgumentException();
}
public static FileAttribute<Set<PosixFilePermission>> asFileAttribute (Set<PosixFilePermission> perms) {
//noinspection ToArrayCallWithZeroLengthArrayArgument
final Set<PosixFilePermission> value = new SearchSet<PosixFilePermission>(perms.toArray(new PosixFilePermission[perms.size()]));
return new FileAttribute<Set<PosixFilePermission>>() {
@Override
public String name() {
return "posix:permissions";
}
@Override
public Set<PosixFilePermission> value () {
return value;
}
};
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
import java.security.Principal;
public interface UserPrincipal extends Principal {
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
import java.io.IOException;
public abstract class UserPrincipalLookupService {
protected UserPrincipalLookupService () {}
public abstract UserPrincipal lookupPrincipalByName (String name) throws IOException;
public abstract GroupPrincipal lookupPrincipalByGroupName (String group) throws IOException;
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.attribute;
import java.io.IOException;
public class UserPrincipalNotFoundException extends IOException {
private final String name;
public UserPrincipalNotFoundException (String name) {
this.name = name;
}
public String getName (){
return name;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.channels;
import java.io.IOException;
import java.nio.channels.ByteChannel;
public interface SeekableByteChannel extends ByteChannel {
public long position () throws IOException;
public SeekableByteChannel position (long newPosition) throws IOException;
public long size () throws IOException;
public SeekableByteChannel truncate (long size) throws IOException;
}

View File

@ -0,0 +1,83 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.DirectoryIteratorException;
import com.llamalab.safs.DirectoryStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
public abstract class AbstractDirectoryStream<T> implements DirectoryStream<T>, Iterator<T> {
private T next;
private boolean started;
private boolean closed;
/**
* Make sure to call super.close();
*/
@Override
public final void close () throws IOException {
if (!closed) {
closed = true;
implCloseStream();
}
}
protected void implCloseStream () throws IOException {}
@SuppressWarnings("NullableProblems")
@Override
public final Iterator<T> iterator () {
if (started)
throw new IllegalStateException();
started = true;
return this;
}
@Override
public final boolean hasNext () {
if (next != null)
return true;
if (closed)
return false;
try {
return (next = advance()) != null;
}
catch (IOException e) {
throw new DirectoryIteratorException(e);
}
}
@Override
public final T next () {
if (closed || !hasNext())
throw new NoSuchElementException();
final T result = next;
next = null;
return result;
}
@Override
public final void remove () {
throw new UnsupportedOperationException();
}
protected abstract T advance () throws IOException;
}

View File

@ -0,0 +1,144 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.LinkOption;
import com.llamalab.safs.NoSuchFileException;
import com.llamalab.safs.OpenOption;
import com.llamalab.safs.Path;
import com.llamalab.safs.ProviderMismatchException;
import com.llamalab.safs.StandardOpenOption;
import com.llamalab.safs.attribute.BasicFileAttributeView;
import com.llamalab.safs.attribute.BasicFileAttributes;
import com.llamalab.safs.attribute.FileAttribute;
import com.llamalab.safs.attribute.FileAttributeView;
import com.llamalab.safs.attribute.FileTime;
import com.llamalab.safs.spi.FileSystemProvider;
import com.llamalab.safs.unix.UnixPath;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Only support {@link UnixPath}.
*/
public abstract class AbstractFileSystemProvider extends FileSystemProvider {
protected static final Set<? extends OpenOption> DEFAULT_NEW_INPUT_STREAM_OPTIONS = EnumSet.of(StandardOpenOption.READ);
protected static final Set<? extends OpenOption> DEFAULT_NEW_OUTPUT_STREAM_OPTIONS = EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
protected abstract Class<? extends Path> getPathType ();
protected void checkPath (Path path) {
if (!getPathType().isInstance(path))
throw (path == null) ? new NullPointerException() : new ProviderMismatchException();
if (path.getFileSystem().provider() != this)
throw new ProviderMismatchException();
}
protected void checkUri (URI uri) {
if (!getScheme().equalsIgnoreCase(uri.getScheme()))
throw new ProviderMismatchException();
}
protected IOException toProperException (IOException ioe, String file, String otherFile) {
return (ioe instanceof FileNotFoundException) ? new NoSuchFileException(file) : ioe;
}
@Override
public Map<String, Object> readAttributes (Path path, String attributes, LinkOption... options) throws IOException {
final Map<String, Object> map = new HashMap<String, Object>();
BasicFileAttributes basic = null;
for (final BasicFileAttribute attribute : BasicFileAttribute.parse(attributes)) {
if (basic == null)
basic = readAttributes(path, BasicFileAttributes.class, options); // read once
map.put(attribute.toString(), attribute.valueOf(basic));
}
return map;
}
@Override
public void setAttribute (Path path, String attribute, Object value, LinkOption... options) throws IOException {
String viewName = BasicFileAttribute.VIEW_NAME;
final int colon = attribute.indexOf(':');
if (colon != -1) {
viewName = attribute.substring(0, colon);
attribute = attribute.substring(colon + 1);
}
final FileAttribute<?> attr = newFileAttribute(viewName, attribute, value);
setAttributes(path, Collections.singleton(attr), options);
}
protected FileAttribute<?> newFileAttribute (String viewName, String attribute, Object value) {
if (BasicFileAttribute.VIEW_NAME.equals(viewName))
return BasicFileAttribute.valueOf(attribute).newFileAttribute(value);
throw new UnsupportedOperationException("Attribute: "+viewName+":"+attribute);
}
protected abstract void setAttributes (Path path, Set<? extends FileAttribute<?>> attrs, LinkOption... options) throws IOException;
@SuppressWarnings("unchecked")
public <V extends FileAttributeView> V getFileAttributeView (final Path path, Class<V> type, final LinkOption... options) {
checkPath(path);
if (BasicFileAttributeView.class == type)
return (V)new BasicFileAttributeViewImpl(path, options);
return null;
}
protected class BasicFileAttributeViewImpl implements BasicFileAttributeView {
protected final Path path;
protected final LinkOption[] options;
public BasicFileAttributeViewImpl (Path path, LinkOption[] options) {
this.path = path;
this.options = options;
}
@Override
public String name () {
return BasicFileAttribute.VIEW_NAME;
}
@Override
public BasicFileAttributes readAttributes () throws IOException {
return AbstractFileSystemProvider.this.readAttributes(path, BasicFileAttributes.class, options);
}
@Override
public void setTimes (FileTime lastModifiedTime, FileTime lastAccessTime, FileTime creationTime) throws IOException {
final Set<FileAttribute<?>> attrs = new HashSet<FileAttribute<?>>();
if (lastModifiedTime != null)
attrs.add(BasicFileAttribute.lastModifiedTime.newFileAttribute(lastModifiedTime));
if (lastAccessTime != null)
attrs.add(BasicFileAttribute.lastAccessTime.newFileAttribute(lastAccessTime));
if (creationTime != null)
attrs.add(BasicFileAttribute.creationTime.newFileAttribute(creationTime));
AbstractFileSystemProvider.this.setAttributes(path, attrs, options);
}
} // class BasicFileAttributeViewImpl
}

View File

@ -0,0 +1,138 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.StandardWatchEventKinds;
import com.llamalab.safs.WatchEvent;
import com.llamalab.safs.WatchKey;
import com.llamalab.safs.Watchable;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractWatchKey implements WatchKey {
private enum State {
READY,
SIGNALLED,
}
private final Object eventLock = new Object();
private final AbstractWatchService service;
private final Watchable watchable;
private final int overflowLimit;
private List<WatchEvent<?>> events = new ArrayList<WatchEvent<?>>();
private State state = State.READY;
public AbstractWatchKey (AbstractWatchService service, Watchable watchable, int overflowLimit) {
this.service = service;
this.watchable = watchable;
this.overflowLimit = overflowLimit;
}
public final AbstractWatchService service () {
return service;
}
@Override
public final Watchable watchable () {
return watchable;
}
@Override
public boolean isValid () {
return service.isOpen();
}
@Override
public final boolean reset () {
synchronized (eventLock) {
if (!isValid())
return false;
if (State.SIGNALLED == state) {
if (events.isEmpty())
state = State.READY;
else
service.offer(this);
}
return true;
}
}
@Override
public final List<WatchEvent<?>> pollEvents () {
synchronized (eventLock) {
final List<WatchEvent<?>> result = events;
events = new ArrayList<WatchEvent<?>>();
return result;
}
}
protected final <T> void signalEvent (WatchEvent.Kind<T> kind, T context) {
synchronized (eventLock) {
final int size = events.size();
if (size > 0) {
final Event<?> event = (Event<?>)events.get(size - 1);
if ( StandardWatchEventKinds.OVERFLOW == event.kind
|| (event.kind == kind && Utils.equals(event.context, context))) {
++event.count;
return;
}
}
if (size < overflowLimit)
events.add(new Event<T>(kind, context));
else
events.add(new Event<Object>(StandardWatchEventKinds.OVERFLOW, null));
if (State.READY == state) {
state = State.SIGNALLED;
service.offer(this);
}
}
}
private static final class Event<T> implements WatchEvent<T> {
private final WatchEvent.Kind<T> kind;
private final T context;
private int count = 1;
public Event (WatchEvent.Kind<T> kind, T context) {
this.kind = kind;
this.context = context;
}
@Override
public Kind<T> kind () {
return kind;
}
@Override
public T context () {
return context;
}
@Override
public int count () {
return count;
}
@Override
public String toString () {
return super.toString()+"[kind="+kind+", context="+context+", count="+count+"]";
}
} // class Event
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.ClosedWatchServiceException;
import com.llamalab.safs.WatchEvent;
import com.llamalab.safs.WatchKey;
import com.llamalab.safs.WatchService;
import com.llamalab.safs.Watchable;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class AbstractWatchService implements WatchService {
private final WatchKey CLOSE_KEY = new WatchKey() {
@Override
public Watchable watchable () {
return null;
}
@Override
public boolean isValid () {
return true;
}
@Override
public boolean reset () {
return true;
}
@Override
public void cancel () {
}
@Override
public List<WatchEvent<?>> pollEvents () {
return null;
}
};
private final LinkedBlockingDeque<WatchKey> pendingKeys = new LinkedBlockingDeque<WatchKey>();
private final AtomicBoolean closed = new AtomicBoolean();
final void offer (WatchKey key) {
pendingKeys.offer(key);
}
@Override
public final WatchKey poll () {
checkOpen();
return checkKey(pendingKeys.poll());
}
@Override
public final WatchKey poll (long timeout, TimeUnit unit) throws InterruptedException {
checkOpen();
return checkKey(pendingKeys.poll(timeout, unit));
}
@Override
public final WatchKey take () throws InterruptedException {
checkOpen();
return checkKey(pendingKeys.take());
}
@Override
public final void close () throws IOException {
if (closed.compareAndSet(false, true)) {
try {
implCloseService();
}
finally {
pendingKeys.clear();
pendingKeys.offer(CLOSE_KEY);
}
}
}
protected abstract void implCloseService () throws IOException;
public final boolean isOpen () {
return !closed.get();
}
private void checkOpen () {
if (closed.get())
throw new ClosedWatchServiceException();
}
private WatchKey checkKey (WatchKey key) {
if (CLOSE_KEY == key)
pendingKeys.offer(key);
checkOpen();
return key;
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import java.util.EnumSet;
import java.util.Iterator;
final class AttributeParser<E extends Enum<E>> implements Iterable<E> {
private final Class<E> enumType;
private final String attributes;
private final String viewName;
private final boolean isDefault;
public AttributeParser (Class<E> enumType, String attributes, String viewName, boolean isDefault) {
this.enumType = enumType;
this.attributes = attributes;
this.viewName = viewName;
this.isDefault = isDefault;
}
@SuppressWarnings("NullableProblems")
@Override
public Iterator<E> iterator () {
final int length = attributes.length();
int start = 0, index = 0;
boolean prefixed = false;
loop: while (index < length) {
switch (attributes.charAt(index)) {
case ':':
if (prefixed)
throw new IllegalArgumentException();
if (index != viewName.length() || !attributes.startsWith(viewName))
return Utils.emptyIterator();
start = ++index;
prefixed = true;
break;
case '*':
if (index != start || ++index != length)
throw new IllegalArgumentException();
if (!prefixed && !isDefault)
return Utils.emptyIterator();
return EnumSet.allOf(enumType).iterator();
case ',':
break loop;
default:
++index;
}
}
if (!prefixed && !isDefault)
return Utils.emptyIterator();
return new CommaSeparatedIterator(start, index);
}
private final class CommaSeparatedIterator implements Iterator<E> {
private int start;
private int index;
public CommaSeparatedIterator (int start, int index) {
this.start = start;
this.index = index;
}
@Override
public boolean hasNext () {
return start < index;
}
@Override
public E next () {
final E element = Enum.valueOf(enumType, attributes.substring(start, index));
index = attributes.indexOf(',', start = index + 1);
if (index == -1)
index = attributes.length();
return element;
}
@Override
public void remove () {
throw new UnsupportedOperationException();
}
} // class CommaSeparatedIterator
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.attribute.BasicFileAttributes;
import com.llamalab.safs.attribute.FileAttribute;
public enum BasicFileAttribute {
fileKey {
@Override
public Object valueOf (BasicFileAttributes attrs) {
return attrs.fileKey();
}
},
isDirectory {
@Override
public Object valueOf (BasicFileAttributes attrs) {
return attrs.isDirectory();
}
},
isRegularFile {
@Override
public Object valueOf (BasicFileAttributes attrs) {
return attrs.isRegularFile();
}
},
isSymbolicLink {
@Override
public Object valueOf (BasicFileAttributes attrs) {
return attrs.isSymbolicLink();
}
},
isOther {
@Override
public Object valueOf (BasicFileAttributes attrs) {
return attrs.isOther();
}
},
size {
@Override
public Object valueOf (BasicFileAttributes attrs) {
return attrs.size();
}
},
creationTime {
@Override
public Object valueOf (BasicFileAttributes attrs) {
return attrs.creationTime();
}
},
lastModifiedTime {
@Override
public Object valueOf (BasicFileAttributes attrs) {
return attrs.lastModifiedTime();
}
},
lastAccessTime {
@Override
public Object valueOf (BasicFileAttributes attrs) {
return attrs.lastAccessTime();
}
};
public static final String VIEW_NAME = "basic";
public abstract Object valueOf (BasicFileAttributes attrs);
public FileAttribute newFileAttribute (Object value) {
return new BasicFileAttributeValue(this, value);
}
public static Iterable<BasicFileAttribute> parse (String attributes) {
return new AttributeParser<BasicFileAttribute>(BasicFileAttribute.class, attributes, VIEW_NAME, true);
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.attribute.FileAttribute;
public final class BasicFileAttributeValue implements FileAttribute<Object> {
private final BasicFileAttribute type;
private final Object value;
public BasicFileAttributeValue (BasicFileAttribute type, Object value) {
if (type == null || value == null)
throw new NullPointerException();
this.type = type;
this.value = value;
}
@Override
public String name () {
return BasicFileAttribute.VIEW_NAME + ":" + type;
}
@Override
public Object value () {
return value;
}
public BasicFileAttribute type () {
return type;
}
@Override
public int hashCode () {
return type.hashCode();
}
@Override
public boolean equals (Object other) {
return other instanceof BasicFileAttributeValue
&& type == ((BasicFileAttributeValue)other).type;
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.attribute.FileTime;
public class CompleteBasicFileAttributes extends PartialBasicFileAttributes {
private final Object fileKey;
private final FileTime creationTime;
private final FileTime lastModifiedTime;
private final FileTime lastAccessTime;
public CompleteBasicFileAttributes (Object fileKey, FileType type, long size, FileTime creationTime, FileTime lastModifiedTime, FileTime lastAccessTime) {
super(type, size);
this.fileKey = fileKey;
this.creationTime = creationTime;
this.lastModifiedTime = lastModifiedTime;
this.lastAccessTime = lastAccessTime;
}
@Override
public final Object fileKey () {
return fileKey;
}
@Override
public final FileTime creationTime () {
return creationTime;
}
@Override
public final FileTime lastModifiedTime () {
return lastModifiedTime;
}
@Override
public final FileTime lastAccessTime () {
return lastAccessTime;
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.Path;
public interface DefaultFileSystem {
public Path getCacheDirectory ();
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
public enum FileType {
DIRECTORY,
REGULAR_FILE,
SYMBOLIC_LINK,
OTHER,
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.attribute.BasicFileAttributes;
public abstract class PartialBasicFileAttributes implements BasicFileAttributes {
private final FileType type;
private final long size;
protected PartialBasicFileAttributes (FileType type, long size) {
this.type = type;
this.size = size;
}
@Override
public final boolean isDirectory () {
return FileType.DIRECTORY == type;
}
@Override
public final boolean isRegularFile () {
return FileType.REGULAR_FILE == type;
}
@Override
public final boolean isSymbolicLink () {
return FileType.SYMBOLIC_LINK == type;
}
@Override
public final boolean isOther () {
return FileType.OTHER == type;
}
@Override
public final long size () {
return size;
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import java.util.Iterator;
public class PathDescender<T extends SegmentEntry> implements Iterator<PathDescender.Event> {
public enum Event {
DIRECTORY,
FILE,
MISSING_DIRECTORY,
MISSING_FILE
}
private final Iterator<String> segments;
private SegmentEntry parent;
private String segment;
private int index;
public PathDescender (T root, Iterator<String> segments) {
this.parent = root;
this.segments = segments;
}
@Override
public boolean hasNext () {
return index >= 0 && segments.hasNext();
}
@Override
public Event next () {
if (segment != null)
parent = parent.children[index];
if ((index = parent.binarySearch(segment = segments.next())) >= 0)
return segments.hasNext() ? Event.DIRECTORY : Event.FILE;
else
return segments.hasNext() ? Event.MISSING_DIRECTORY : Event.MISSING_FILE;
}
@Override
public void remove () {
parent.remove(index);
index = -1;
}
public void set (T entry) {
entry.segment = segment;
index = parent.put(index, entry);
}
public String segment () {
return segment;
}
@SuppressWarnings("unchecked")
public T parent () {
return (T)parent;
}
@SuppressWarnings("unchecked")
public T entry () {
return (T)parent.children[index];
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Values must MUST implement Comparable.
*/
public class SearchSet<T> extends AbstractSet<T> implements Comparator<Object> {
private final Object[] elements;
@SafeVarargs
public SearchSet (T... elements) {
this.elements = elements;
Arrays.sort(elements, this);
}
@SuppressWarnings("NullableProblems")
@Override
public Iterator<T> iterator () {
return new Iterator<T>() {
private int index;
@Override
public boolean hasNext () {
return index < elements.length;
}
@SuppressWarnings("unchecked")
@Override
public T next () {
if (index >= elements.length)
throw new NoSuchElementException();
return (T)elements[index++];
}
@Override
public void remove () {
throw new UnsupportedOperationException();
}
};
}
@Override
public int size () {
return elements.length;
}
@SuppressWarnings("NullableProblems")
@Override
public boolean addAll (Collection<? extends T> collection) {
throw new UnsupportedOperationException();
}
@Override
public void clear () {
throw new UnsupportedOperationException();
}
@Override
public boolean contains (Object object) {
return Arrays.binarySearch(elements, object, this) >= 0;
}
@Override
public boolean remove (Object object) {
throw new UnsupportedOperationException();
}
@SuppressWarnings("NullableProblems")
@Override
public boolean retainAll (Collection<?> collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll (Collection<?> collection) {
throw new UnsupportedOperationException();
}
@SuppressWarnings("unchecked")
@Override
public int compare (Object lhs, Object rhs) {
if (lhs == rhs)
return 0;
if (lhs == null)
return 1;
if (rhs == null)
return -1;
final Class lc = lhs.getClass();
final Class rc = rhs.getClass();
return (lc != rc) ? lc.getName().compareTo(rc.getName()) : ((Comparable)lhs).compareTo(rhs);
}
}

View File

@ -0,0 +1,150 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.unix.UnixPath;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class SegmentEntry<T extends SegmentEntry<T>> implements Iterable<T> {
private static final SegmentEntry[] EMPTY = new SegmentEntry[0];
String segment;
SegmentEntry[] children = EMPTY;
int size;
@SuppressWarnings("NullableProblems")
@Override
public Iterator<T> iterator () {
return new Iterator<T>() {
private int index;
@Override
public boolean hasNext () {
return index < size;
}
@SuppressWarnings("unchecked")
@Override
public T next () {
if (index >= size)
throw new NoSuchElementException();
return (T)children[index++];
}
@Override
public void remove () {
if (index == 0)
throw new IllegalStateException();
SegmentEntry.this.remove(--index);
}
};
}
public final boolean isEmpty () {
return size == 0;
}
public final void clear () {
size = 0;
Arrays.fill(children, null);
}
@SuppressWarnings({ "unchecked", "SuspiciousSystemArraycopy" })
public T[] toArray (T[] a) {
if (size > a.length)
a = (T[])Array.newInstance(a.getClass().getComponentType(), size);
System.arraycopy(children, 0, a, 0, size);
return a;
}
public T getDescendant (UnixPath path) {
return getDescendant(path.stringIterator());
}
@SuppressWarnings("unchecked")
public T getDescendant (Iterator<String> segments) {
SegmentEntry parent = this;
while (segments.hasNext()) {
final int index = parent.binarySearch(segments.next());
if (index < 0)
return null;
parent = parent.children[index];
}
return (T)parent;
}
public final PathDescender<T> descentor (UnixPath path) {
return descentor(path.stringIterator());
}
@SuppressWarnings("unchecked")
public final PathDescender<T> descentor (Iterator<String> segments) {
return new PathDescender<T>((T)this, segments);
}
final int binarySearch (String segment) {
final SegmentEntry[] c = children;
int low = 0;
int high = size - 1;
while (low <= high) {
final int mid = (low + high) >>> 1;
final int cmp = c[mid].segment.compareTo(segment);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
final int put (int index, SegmentEntry child) {
if (index < 0) {
index = ~index;
final SegmentEntry[] oc = children;
final int s = size++;
if (s == oc.length) {
final SegmentEntry[] nc = new SegmentEntry[1 << (32 - Integer.numberOfLeadingZeros(s))];
System.arraycopy(oc, 0, nc, 0, index);
System.arraycopy(oc, index, nc, index + 1, s - index);
children = nc;
nc[index] = child;
}
else {
System.arraycopy(oc, index, oc, index + 1, s - index);
oc[index] = child;
}
}
else
children[index] = child;
return index;
}
final void remove (int index) {
if (index < 0 || size <= index)
throw new IndexOutOfBoundsException();
System.arraycopy(children, index + 1, children, index, --size - index);
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.attribute.GroupPrincipal;
import com.llamalab.safs.attribute.UserPrincipal;
import com.llamalab.safs.attribute.UserPrincipalLookupService;
import java.io.IOException;
public class UserPrincipalFactory extends UserPrincipalLookupService {
@Override
public UserPrincipal lookupPrincipalByName (String name) throws IOException {
return new User(name);
}
@Override
public GroupPrincipal lookupPrincipalByGroupName (String group) throws IOException {
return new Group(group);
}
protected static class User implements UserPrincipal {
private final String name;
public User (String name) {
if (name == null)
throw new NullPointerException();
this.name = name;
}
@Override
public String getName () {
return name;
}
@Override
public String toString () {
return super.toString()+"[name="+name+"]";
}
@Override
public boolean equals (Object other) {
return other instanceof User && name.equals(((User)other).name);
}
@Override
public int hashCode () {
return name.hashCode();
}
}
protected static class Group extends User implements GroupPrincipal {
public Group (String name) {
super(name);
}
}
}

View File

@ -0,0 +1,455 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.CopyOption;
import com.llamalab.safs.DirectoryStream;
import com.llamalab.safs.FileVisitOption;
import com.llamalab.safs.FileVisitResult;
import com.llamalab.safs.FileVisitor;
import com.llamalab.safs.Files;
import com.llamalab.safs.LinkOption;
import com.llamalab.safs.OpenOption;
import com.llamalab.safs.Path;
import com.llamalab.safs.SimpleFileVisitor;
import com.llamalab.safs.attribute.BasicFileAttributes;
import com.llamalab.safs.attribute.FileTime;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
@SuppressWarnings("unused")
public final class Utils {
private static final int BUFFER_SIZE = 8192;
public static final Charset UTF_8 = Charset.forName("utf-8");
public static final Charset US_ASCII = Charset.forName("US-ASCII");
public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
public static final FileTime ZERO_TIME = FileTime.fromMillis(0);
public static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final CharSequence[] EMPTY_CHAR_SEQUENCE_ARRAY = new CharSequence[0];
public static final CopyOption[] EMPTY_COPY_OPTION_ARRAY = new CopyOption[0];
public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = new LinkOption[0];
public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = new FileVisitOption[0];
public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = new OpenOption[0];
public static final DirectoryStream.Filter<Path> ACCEPT_ALL_FILTER = new DirectoryStream.Filter<Path> () {
@Override
public boolean accept (Path entry) throws IOException {
return true;
}
};
public static final FileVisitor<Path> DELETE_FILE_VISITOR = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult postVisitDirectory (Path dir, IOException e) throws IOException {
if (e != null)
throw e;
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile (Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed (Path file, IOException e) throws IOException {
return FileVisitResult.CONTINUE;
}
};
private static final Iterator<?> EMPTY_ITERATOR = new Iterator<Object> () {
@Override
public boolean hasNext () {
return false;
}
@Override
public Object next () {
return null;
}
@Override
public void remove () {
throw new UnsupportedOperationException();
}
};
private Utils () {}
public static boolean equals (Object a, Object b) {
return a != null ? a.equals(b) : b == null;
}
public static boolean contentEquals (CharSequence cs1, CharSequence cs2) {
int l = cs1.length();
if (l != cs2.length())
return false;
for (int i = 0; --l >= 0; ++i) {
if (cs1.charAt(i) != cs2.charAt(i))
return false;
}
return true;
}
public static boolean contentEqualsIgnoreCase (CharSequence cs1, CharSequence cs2) {
int l = cs1.length();
if (l != cs2.length())
return false;
for (int i = 0; --l >= 0; ++i) {
char c1 = cs1.charAt(i);
char c2 = cs2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2)
return false;
}
}
}
return true;
}
/**
* Available in Java 1.8
* @see java.lang.String#join(CharSequence, Iterable)
*/
public static String join (CharSequence delimiter, Iterable<? extends CharSequence> elements) {
final StringBuilder sb = new StringBuilder();
CharSequence d = "";
for (final CharSequence element : elements) {
sb.append(d).append(element);
d = delimiter;
}
return sb.toString();
}
/**
* Available in Java 1.7 (Android 4.4)
* @see java.util.Collections#emptyIterator()
*/
@SuppressWarnings("unchecked")
public static <E> Iterator<E> emptyIterator () {
return (Iterator<E>)EMPTY_ITERATOR;
}
public static <E> Iterator<E> singletonIterator (final E element) {
return new Iterator<E>() {
private boolean started;
@Override
public boolean hasNext () {
return !started;
}
@Override
public E next () {
if (started)
throw new NoSuchElementException();
started = true;
return element;
}
@Override
public void remove () {
throw new UnsupportedOperationException();
}
};
}
public static <T> DirectoryStream<T> singletonDirectoryStream (final T entry) {
return new AbstractDirectoryStream<T>() {
private boolean started;
@Override
protected T advance () throws IOException {
if (started)
return null;
started = true;
return entry;
}
};
}
public static <T> List<T> listOf (Iterator<T> i) {
final List<T> list = new ArrayList<T>();
while (i.hasNext())
list.add(i.next());
return list;
}
public static <T> List<T> listOf (Iterable<T> i) {
return listOf(i.iterator());
}
public static void closeQuietly (Closeable c) {
try {
c.close();
}
catch (Throwable t) {
// ignore
}
}
/*
public static void unmap (MappedByteBuffer buf) {
try {
final Object cleaner = buf.getClass().getMethod("cleaner").invoke(buf);
cleaner.getClass().getMethod("clean").invoke(cleaner);
}
catch (Throwable t) {
// ignore
}
}
*/
public static long transfer (ReadableByteChannel in, WritableByteChannel out) throws IOException {
if (in instanceof FileChannel)
return transfer((FileChannel)in, out);
else
return transfer(in, out, ByteBuffer.allocate(BUFFER_SIZE));
}
public static long transfer (ReadableByteChannel in, WritableByteChannel out, ByteBuffer buf) throws IOException {
long written = 0;
while (in.read(buf) != -1) {
buf.flip();
while (buf.hasRemaining())
written += out.write(buf);
buf.clear();
}
return written;
}
public static long transfer (FileChannel in, WritableByteChannel out) throws IOException {
long remaining = in.size();
long written = 0;
long b;
while (remaining > 0) {
b = in.transferTo(written, remaining, out);
remaining -= b;
written += b;
}
return written;
}
public static long transfer (InputStream in, OutputStream out) throws IOException {
return transfer(in, out, new byte[BUFFER_SIZE]);
}
public static long transfer (InputStream in, OutputStream out, byte[] buf) throws IOException {
long written = 0;
int b;
while ((b = in.read(buf)) != -1) {
out.write(buf, 0, b);
written += b;
}
return written;
}
public static byte[] readAllBytes (InputStream in, int capacity) throws IOException {
byte[] data = new byte[capacity];
int size = 0;
for (int b;;) {
while ((b = in.read(data, size, capacity - size)) > 0)
size += b;
if (b < 0 || (b = in.read()) < 0)
break;
if (capacity == Integer.MAX_VALUE)
throw new OutOfMemoryError("Array size exceeded");
capacity = (int)Math.min(Math.max(capacity * 2L, BUFFER_SIZE), Integer.MAX_VALUE);
data = Arrays.copyOf(data, capacity);
data[size++] = (byte)b;
}
return (size == data.length) ? data : Arrays.copyOf(data, size);
}
// http://stackoverflow.com/a/522281/445360
private static final Pattern RFC3339 = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?(?:Z|([+-]\\d{2}:\\d{2}))");
public static long parseRfc3339 (CharSequence text) {
final Matcher m = RFC3339.matcher(text);
if (!m.matches())
throw new IllegalArgumentException();
String g = m.group(8);
final GregorianCalendar gc = new GregorianCalendar(TimeZone.getTimeZone((g != null) ? g : "UTC"), Locale.US);
//noinspection MagicConstant
gc.set(
Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)) - 1, Integer.parseInt(m.group(3)),
Integer.parseInt(m.group(4)), Integer.parseInt(m.group(5)), Integer.parseInt(m.group(6)));
g = m.group(7);
gc.set(Calendar.MILLISECOND, (g != null) ? Integer.parseInt(g)%1000 : 0);
return gc.getTimeInMillis();
}
public static String formatRfc3339 (long millis) {
final GregorianCalendar gc = new GregorianCalendar(Utils.UTC, Locale.US);
gc.setTimeInMillis(millis);
//noinspection MagicConstant
return String.format(Locale.US, (gc.get(Calendar.MILLISECOND) != 0) ? "%1$tFT%1$tT.%1$tLZ" : "%1$tFT%1$tTZ", gc);
}
/*
public static String formatRfc3339 (long time) {
final GregorianCalendar gc = new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.US);
gc.setTimeInMillis(time);
return String.format(Locale.US, "%04d-%02d-%02dT%02d:%02d:%02d.%dZ",
gc.get(Calendar.YEAR), gc.get(Calendar.MONTH) + 1, gc.get(Calendar.DAY_OF_MONTH),
gc.get(Calendar.HOUR_OF_DAY), gc.get(Calendar.MINUTE), gc.get(Calendar.SECOND), gc.get(Calendar.MILLISECOND));
}
*/
public static FileTime parseFileTime (CharSequence text) {
return FileTime.fromMillis(parseRfc3339(text));
}
public static String getFileExtension (Path path) {
final Path fileName = path.getFileName();
if (fileName != null) {
final String name = fileName.toString();
final int i = name.lastIndexOf('.');
if (i > 0 && i < name.length() - 1)
return name.substring(i + 1).toLowerCase(Locale.US);
}
return null;
}
/** Must be sorted! */
private static final char[] REGEX_ESCAPEES = { '$', '(', ')', '*', '+', '.', '?', '[', '\\', ']', '^', '{', '|' };
/**
* Only works with unix path separator (/).
*/
public static String globToRegex (String glob, int s, int e) {
final StringBuilder regex = new StringBuilder("^");
int g = -1;
char c;
while (s < e) {
switch (c = glob.charAt(s++)) {
case '?':
regex.append("[^/]");
break;
case '*':
if (s < e && '*' == glob.charAt(s)) {
regex.append(".*");
++s;
}
else
regex.append("[^/]*");
break;
case '[':
regex.append("[[");
c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid class"));
if ('^' == c) {
regex.append("\\^");
c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid class"));
}
else {
if ('!' == c) {
regex.append('^');
c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid class"));
}
if ('-' == c) {
regex.append('-');
c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid class"));
}
}
int l = Integer.MAX_VALUE;
while (']' != c) {
if ('/' == c)
throw new PatternSyntaxException("Invalid class character: /", glob, s - 1);
regex.append(c);
if ('-' == c) {
c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid range"));
if (c < l)
throw new PatternSyntaxException("Invalid range", glob, s - 3);
l = Integer.MAX_VALUE;
continue;
}
else
l = c;
c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid range"));
}
regex.append("]&&[^/]]");
break;
case '{':
if (g != -1)
throw new PatternSyntaxException("Nested group", glob, s - 1);
regex.append("(?:");
g = s;
break;
case ',':
regex.append((g != -1) ? '|' : ',');
break;
case '}':
regex.append((g != -1) ? ')' : '}');
g = -1;
break;
case '\\':
c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid escape"));
// fall thu
default:
if (Arrays.binarySearch(REGEX_ESCAPEES, c) >= 0)
regex.append("\\");
regex.append(c);
}
}
if (g != -1)
throw new PatternSyntaxException("Invalid group", glob, g - 1);
return regex.append('$').toString();
}
private static int checkGlobEnd (String glob, int s, int e, String message) {
if (s == e)
throw new PatternSyntaxException(message, glob, s - 1);
return s;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.internal;
import com.llamalab.safs.WatchEvent;
public class WatchEventKind<T> implements WatchEvent.Kind<T> {
private final String name;
private final Class<T> type;
public WatchEventKind (String name, Class<T> type) {
this.name = name;
this.type = type;
}
@Override
public final String name () {
return name;
}
@Override
public final Class<T> type () {
return type;
}
@Override
public final String toString () {
return name;
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.java;
import com.llamalab.safs.FileSystem;
import com.llamalab.safs.FileSystemAlreadyExistsException;
import com.llamalab.safs.Path;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
public final class DefaultJavaFileSystemProvider extends JavaFileSystemProvider {
private final FileSystem fileSystem = new JavaFileSystem(this);
@Override
public FileSystem getFileSystem (URI uri) {
checkUri(uri);
return fileSystem;
}
@Override
public FileSystem newFileSystem (URI uri, Map<String,?> env) throws IOException {
checkUri(uri);
throw new FileSystemAlreadyExistsException();
}
@Override
public FileSystem newFileSystem (Path path, Map<String,?> env) throws IOException {
checkPath(path);
throw new FileSystemAlreadyExistsException();
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.java;
import com.llamalab.safs.LinkOption;
import com.llamalab.safs.NoSuchFileException;
import com.llamalab.safs.Path;
import com.llamalab.safs.internal.DefaultFileSystem;
import com.llamalab.safs.spi.FileSystemProvider;
import com.llamalab.safs.unix.AbstractUnixFileSystem;
import java.io.File;
import java.io.IOException;
/**
* Only support {@link com.llamalab.safs.unix.UnixPath}.
*/
public class JavaFileSystem extends AbstractUnixFileSystem implements DefaultFileSystem {
protected volatile Path cacheDirectory;
protected volatile Path currentDirectory;
public JavaFileSystem (FileSystemProvider provider) {
super(provider);
}
@Override
public final void close () throws IOException {
throw new UnsupportedOperationException();
}
@Override
public final boolean isOpen () {
return true;
}
@Override
public boolean isReadOnly () {
return false;
}
@Override
public Path getCacheDirectory () {
if (cacheDirectory == null)
cacheDirectory = getPathSanitized(System.getProperty("java.io.tmpdir"));
return cacheDirectory;
}
public final Path getCurrentDirectory () {
if (currentDirectory == null)
currentDirectory = getPathSanitized(System.getProperty("user.dir"));
return currentDirectory;
}
@Override
protected Path toRealPath (Path path, LinkOption... options) throws IOException {
final File file = path.toFile();
if (!file.exists())
throw new NoSuchFileException(path.toString());
for (final LinkOption option : options) {
if (LinkOption.NOFOLLOW_LINKS == option)
return path.toAbsolutePath().normalize();
}
return getPathSanitized(file.getCanonicalPath());
}
}

View File

@ -0,0 +1,490 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.java;
import com.llamalab.safs.AccessDeniedException;
import com.llamalab.safs.AtomicMoveNotSupportedException;
import com.llamalab.safs.CopyOption;
import com.llamalab.safs.DirectoryNotEmptyException;
import com.llamalab.safs.DirectoryStream;
import com.llamalab.safs.FileAlreadyExistsException;
import com.llamalab.safs.FileStore;
import com.llamalab.safs.FileSystemException;
import com.llamalab.safs.LinkOption;
import com.llamalab.safs.NoSuchFileException;
import com.llamalab.safs.NotDirectoryException;
import com.llamalab.safs.NotLinkException;
import com.llamalab.safs.OpenOption;
import com.llamalab.safs.Path;
import com.llamalab.safs.StandardCopyOption;
import com.llamalab.safs.StandardOpenOption;
import com.llamalab.safs.attribute.BasicFileAttributes;
import com.llamalab.safs.attribute.FileAttribute;
import com.llamalab.safs.attribute.FileTime;
import com.llamalab.safs.channels.SeekableByteChannel;
import com.llamalab.safs.internal.AbstractDirectoryStream;
import com.llamalab.safs.internal.BasicFileAttributeValue;
import com.llamalab.safs.internal.CompleteBasicFileAttributes;
import com.llamalab.safs.internal.FileType;
import com.llamalab.safs.internal.SearchSet;
import com.llamalab.safs.internal.Utils;
import com.llamalab.safs.spi.FileSystemProvider;
import com.llamalab.safs.unix.AbstractUnixFileSystemProvider;
import com.llamalab.safs.unix.UnixPath;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.Set;
/**
* Only support {@link UnixPath}.
*/
public abstract class JavaFileSystemProvider extends AbstractUnixFileSystemProvider {
public JavaFileSystemProvider () {}
public JavaFileSystemProvider (FileSystemProvider provider) {}
@Override
public String getScheme () {
return "file";
}
@Override
public FileStore getFileStore (Path path) throws IOException {
// TODO: possible?
throw new UnsupportedOperationException();
}
@Override
public boolean isSameFile (Path path1, Path path2) throws IOException {
if (path1.equals(path2))
return true;
return getPathType().isInstance(path1) && getPathType().isInstance(path2)
&& path1.getFileSystem().equals(path2.getFileSystem())
&& isSameFile(path1.toFile(), path2.toFile());
}
@Override
public boolean isHidden (Path path) throws IOException {
checkPath(path);
return ((UnixPath)path).isHidden();
}
@Override
public void createDirectory (Path dir, FileAttribute<?>... attrs) throws IOException {
checkPath(dir);
createDirectory(dir.toFile(), attrs);
}
@Override
public void delete (Path path) throws IOException {
checkPath(path);
delete(path.toFile(), false);
}
@Override
public void copy (Path source, Path target, CopyOption... options) throws IOException {
checkPath(source);
checkPath(target);
transfer(source, target, false, new SearchSet<CopyOption>(options));
}
@Override
public void move (Path source, Path target, CopyOption... options) throws IOException {
checkPath(source);
checkPath(target);
transfer(source, target, true, new SearchSet<CopyOption>(options));
}
// TODO: symbolic links
private void transfer (Path source, Path target, boolean move, Set<CopyOption> options) throws IOException {
final File sourceFile = source.toFile();
final BasicFileAttributes sourceAttrs = readBasicFileAttributes(sourceFile);
final File targetFile = target.toFile();
if (sourceFile.getCanonicalPath().equals(targetFile.getCanonicalPath()))
return;
// atomic
if (move && options.contains(StandardCopyOption.ATOMIC_MOVE)) {
if (sourceFile.renameTo(targetFile))
return;
throw new AtomicMoveNotSupportedException(source.toString(), target.toString(), "Rename failed");
}
// delete target
if (options.contains(StandardCopyOption.REPLACE_EXISTING))
delete(targetFile, true); // throws DirectoryNotEmptyException
else if (targetFile.exists())
throw new FileAlreadyExistsException(target.toString());
// rename
if (move && sourceFile.renameTo(targetFile))
return;
// transfer
if (sourceAttrs.isDirectory()) {
if (move && isNonEmptyDirectory(sourceFile))
throw new DirectoryNotEmptyException(source.toString());
createDirectory(target);
}
else
copyFile(sourceFile, targetFile);
try {
if (options.contains(StandardCopyOption.COPY_ATTRIBUTES))
setLastModifiedTime(targetFile, sourceAttrs.lastModifiedTime());
if (move)
delete(sourceFile, false);
}
catch (IOException e) {
//noinspection ResultOfMethodCallIgnored
targetFile.delete();
throw e;
}
catch (RuntimeException e) {
//noinspection ResultOfMethodCallIgnored
targetFile.delete();
throw e;
}
}
@Override
public InputStream newInputStream (Path path, OpenOption... options) throws IOException {
checkPath(path);
return newInputStream(path.toFile(), (options.length == 0) ? DEFAULT_NEW_INPUT_STREAM_OPTIONS : new SearchSet<OpenOption>(options));
}
// TODO: LinkOption.NOFOLLOW_LINKS
private InputStream newInputStream (File file, Set<? extends OpenOption> options) throws IOException {
if (options.contains(StandardOpenOption.WRITE))
throw new IllegalArgumentException();
try {
return new FileInputStream(file);
}
catch (IOException e) {
throw toProperException(e, file.toString(), null);
}
}
@Override
public OutputStream newOutputStream (Path path, OpenOption... options) throws IOException {
checkPath(path);
return newOutputStream(path.toFile(), (options.length == 0) ? DEFAULT_NEW_OUTPUT_STREAM_OPTIONS : new SearchSet<OpenOption>(options));
}
// TODO: LinkOption.NOFOLLOW_LINKS
private OutputStream newOutputStream (File file, Set<? extends OpenOption> options) throws IOException {
if (!options.contains(StandardOpenOption.WRITE))
throw new IllegalArgumentException();
try {
checkCreateOptions(file, options);
return new FileOutputStream(file, options.contains(StandardOpenOption.APPEND));
}
catch (IOException e) {
throw toProperException(e, file.toString(), null);
}
}
@Override
public SeekableByteChannel newByteChannel (Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
checkPath(path);
return newByteChannel(path.toFile(), options, attrs);
}
// TODO: LinkOption.NOFOLLOW_LINKS
private SeekableByteChannel newByteChannel (File file, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
try {
if (!options.contains(StandardOpenOption.WRITE))
return new SeekableByteChannelWrapper(new RandomAccessFile(file, "r").getChannel(), false);
checkCreateOptions(file, options);
final RandomAccessFile raf = new RandomAccessFile(file, toModeString(options));
if (options.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
try {
raf.setLength(0);
}
catch (IOException e) {
Utils.closeQuietly(raf);
throw e;
}
}
return new SeekableByteChannelWrapper(raf.getChannel(), options.contains(StandardOpenOption.APPEND));
}
catch (IOException e) {
throw toProperException(e, file.toString(), null);
}
}
private static String toModeString (Set<? extends OpenOption> options) {
if (options.contains(StandardOpenOption.SYNC))
return "rws";
if (options.contains(StandardOpenOption.DSYNC))
return "rwd";
return "rw";
}
private static void checkCreateOptions (File file, Set<? extends OpenOption> options) throws IOException {
if (options.contains(StandardOpenOption.CREATE_NEW)) {
if (file.exists())
throw new FileAlreadyExistsException(file.toString());
}
else if (!options.contains(StandardOpenOption.CREATE)) {
if (!file.exists())
throw new NoSuchFileException(file.toString());
}
}
/*
private RandomAccessFile newRandomAccessFile (File file, Set<? extends OpenOption> options) throws IOException {
try {
if (!options.contains(StandardOpenOption.WRITE))
return new RandomAccessFile(file, toModeString(options));
final RandomAccessFile raf = new RandomAccessFile(file, toModeString(options));
if (options.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
try {
raf.setLength(0);
}
catch (IOException e) {
Utils.closeQuietly(raf);
throw e;
}
}
return raf;
}
catch (IOException e) {
throw toProperException(e, file.toString(), null);
}
}
*/
/*
@Override
public void createSymbolicLink (Path link, Path target) throws IOException {
checkPaths(link, target);
createSymbolicLink(link.toFile(), target.toFile());
}
protected void createSymbolicLink (File link, File target) throws IOException {
final Process process = Runtime.getRuntime().exec(new String[] { "ln", "-s", target.toString(), link.toString() });
try {
if (0 != process.waitFor()) {
if (target.exists())
throw new FileAlreadyExistsException(target.toString());
else
throw new IOException("ln command failure");
}
}
catch (InterruptedException e) {
throw (IOException)new InterruptedIOException().initCause(e);
}
finally {
process.destroy();
}
}
*/
@Override
public Path readSymbolicLink (Path link) throws IOException {
final Path parent = link.getParent();
if (parent != null)
link = parent.toRealPath().resolve(link.getFileName());
final Path real = link.toRealPath();
if (real.equals(link.toAbsolutePath()))
throw new NotLinkException(link.toString());
return real;
}
/**
* May be overridden with a faster implementation.
*/
protected boolean isSymbolicLink (Path path) {
return isSymbolicLink(path.toFile());
}
private boolean isSymbolicLink (File file) {
try {
final File parent = file.getParentFile();
if (parent != null)
file = new File(parent.getCanonicalPath(), file.getName());
return !file.getCanonicalPath().equals(file.getAbsolutePath());
}
catch (IOException e) {
return false;
}
}
@SuppressWarnings("unchecked")
@Override
public <A extends BasicFileAttributes> A readAttributes (Path path, Class<A> type, LinkOption... options) throws IOException {
checkPath(path);
if (BasicFileAttributes.class != type)
throw new UnsupportedOperationException("Unsupported type: "+type);
return (A)readBasicFileAttributes(path.toFile(), options);
}
protected BasicFileAttributes readBasicFileAttributes (File file, LinkOption... options) throws IOException {
for (final LinkOption option : options) {
if (LinkOption.NOFOLLOW_LINKS == option) {
if (!isSymbolicLink(file))
break;
return new CompleteBasicFileAttributes(null, FileType.SYMBOLIC_LINK, 0, Utils.ZERO_TIME, Utils.ZERO_TIME, Utils.ZERO_TIME);
}
}
if (!file.exists())
throw new NoSuchFileException(file.toString());
final FileType fileType;
if (file.isDirectory())
fileType = FileType.DIRECTORY;
else if (file.isFile())
fileType = FileType.REGULAR_FILE;
else
fileType = FileType.OTHER;
return new CompleteBasicFileAttributes(
null,
fileType,
file.length(),
Utils.ZERO_TIME,
FileTime.fromMillis(file.lastModified()),
Utils.ZERO_TIME);
}
@Override
protected void setAttributes (Path path, Set<? extends FileAttribute<?>> attrs, LinkOption... options) throws IOException {
checkPath(path);
for (final FileAttribute<?> attr : attrs) {
if (attr instanceof BasicFileAttributeValue) {
switch (((BasicFileAttributeValue)attr).type()) {
case lastModifiedTime:
setLastModifiedTime(path.toFile(), (FileTime)attr.value());
continue;
}
}
throw new UnsupportedOperationException("Attribute: "+attr.name());
}
}
protected void setLastModifiedTime (File file, FileTime value) throws IOException {
if (!file.setLastModified(value.toMillis()))
throw new FileSystemException(file.toString(), null, "Failed to set last modified time");
}
@Override
public DirectoryStream<Path> newDirectoryStream (final Path dir, final DirectoryStream.Filter<? super Path> filter) throws IOException {
checkPath(dir);
if (filter == null)
throw new NullPointerException("filter");
try {
final File dirFile = dir.toFile();
final String[] files = dirFile.list();
if (files == null) {
if (dirFile.exists() && !dirFile.canRead())
throw new AccessDeniedException(dir.toString());
throw new NotDirectoryException(dir.toString());
}
return new AbstractDirectoryStream<Path>() {
private int index;
@Override
protected Path advance () throws IOException {
while (index < files.length) {
final Path entry = dir.resolve(files[index++]);
if (filter.accept(entry))
return entry;
}
return null;
}
};
}
catch (IOException e) {
throw toProperException(e, dir.toString(), null);
}
}
protected final void createDirectory (File dir, FileAttribute<?>... attrs) throws IOException {
if (!dir.mkdir()) {
if (dir.exists()) {
if (!dir.canWrite())
throw new AccessDeniedException(dir.toString());
throw new FileAlreadyExistsException(dir.toString());
}
throw new FileSystemException(dir.toString(), null, "Failed to create directory");
}
}
protected final void delete (File file, boolean ifExists) throws IOException {
if (!file.delete()) {
if (isNonEmptyDirectory(file))
throw new DirectoryNotEmptyException(file.toString());
if (file.exists()) {
if (!file.canWrite())
throw new AccessDeniedException(file.toString());
throw new FileSystemException(file.toString(), null, "Failed to delete file");
}
else if (!ifExists)
throw new NoSuchFileException(file.toString());
}
}
protected final void copyFile (File source, File target) throws IOException {
final FileChannel in;
try {
in = new FileInputStream(source).getChannel();
}
catch (IOException e) {
throw toProperException(e, source.toString(), null);
}
try {
final FileChannel out;
try {
out = new FileOutputStream(target).getChannel();
}
catch (IOException e) {
throw toProperException(e, target.toString(), null);
}
try {
Utils.transfer(in, out);
}
catch (IOException e) {
throw toProperException(e, source.toString(), target.toString());
}
finally {
out.close();
}
}
finally {
in.close();
}
}
protected final boolean isSameFile (File file1, File file2) throws IOException {
return file1.getCanonicalPath().equals(file2.getCanonicalPath());
}
protected final boolean isNonEmptyDirectory (File dir) {
final String[] names = dir.list(new FilenameFilter() {
private boolean found;
@Override
public boolean accept (File dir, String filename) {
if (found)
return false;
return found = true;
}
});
return names != null && names.length != 0;
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.java;
import com.llamalab.safs.channels.SeekableByteChannel;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
final class SeekableByteChannelWrapper implements SeekableByteChannel {
private final FileChannel fc;
private final boolean append;
public SeekableByteChannelWrapper (FileChannel fc, boolean append) {
this.fc = fc;
this.append = append;
}
@Override
public void close () throws IOException {
fc.close();
}
@Override
public boolean isOpen () {
return fc.isOpen();
}
@Override
public int read (ByteBuffer dst) throws IOException {
return fc.read(dst);
}
@Override
public int write (ByteBuffer src) throws IOException {
if (append)
fc.position(fc.size());
return fc.write(src);
}
@Override
public long position () throws IOException {
return fc.position();
}
@Override
public SeekableByteChannel position (long newPosition) throws IOException {
fc.position(newPosition);
return this;
}
@Override
public long size () throws IOException {
return fc.size();
}
@Override
public SeekableByteChannel truncate (long size) throws IOException {
fc.truncate(size);
return this;
}
}

View File

@ -0,0 +1,28 @@
package com.llamalab.safs.kotlin.io.path
import com.llamalab.safs.LinkOption
import com.llamalab.safs.Path
import com.llamalab.safs.Files
import com.llamalab.safs.OpenOption
import com.llamalab.safs.attribute.FileAttribute
import java.nio.charset.Charset
public inline fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options)
public inline fun Path.readBytes(): ByteArray {
return Files.readAllBytes(this)
}
public inline fun Path.writeBytes(array: ByteArray, vararg options: OpenOption): Unit {
Files.write(this, array, *options)
}
public inline fun Path.createDirectories(vararg attributes: FileAttribute<*>): Path =
Files.createDirectories(this, *attributes)
public fun createTempDirectory(directory: Path?, prefix: String? = null, vararg attributes: FileAttribute<*>): Path =
if (directory != null)
Files.createTempDirectory(directory, prefix, *attributes)
else
Files.createTempDirectory(prefix, *attributes)
public inline fun createTempDirectory(prefix: String? = null, vararg attributes: FileAttribute<*>): Path = Files.createTempDirectory(prefix, *attributes)
public fun Path.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption) {
Files.newOutputStream(this, *options).writer(charset).use { it.append(text) }
}

View File

@ -0,0 +1,106 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.spi;
import com.llamalab.safs.CopyOption;
import com.llamalab.safs.DirectoryStream;
import com.llamalab.safs.FileStore;
import com.llamalab.safs.FileSystem;
import com.llamalab.safs.FileSystems;
import com.llamalab.safs.LinkOption;
import com.llamalab.safs.NoSuchFileException;
import com.llamalab.safs.OpenOption;
import com.llamalab.safs.Path;
import com.llamalab.safs.attribute.BasicFileAttributes;
import com.llamalab.safs.attribute.FileAttribute;
import com.llamalab.safs.attribute.FileAttributeView;
import com.llamalab.safs.channels.SeekableByteChannel;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
public abstract class FileSystemProvider {
protected FileSystemProvider () {}
public static List<FileSystemProvider> installedProviders () {
return InstalledFileSystemProvidersHolder.providers;
}
public abstract String getScheme ();
public abstract Path getPath (URI uri);
public abstract FileSystem getFileSystem (URI uri);
public abstract FileSystem newFileSystem (Path path, Map<String,?> env) throws IOException;
public abstract FileSystem newFileSystem (URI uri, Map<String,?> env) throws IOException;
public abstract FileStore getFileStore (Path path) throws IOException;
public abstract boolean isSameFile (Path path1, Path path2) throws IOException;
public abstract boolean isHidden (Path path) throws IOException;
public abstract void createDirectory (Path dir, FileAttribute<?>... attrs) throws IOException;
public abstract void delete (Path path) throws IOException;
public boolean deleteIfExists (Path path) throws IOException {
try {
delete(path);
return true;
}
catch (NoSuchFileException e) {
return false;
}
}
public abstract void copy (Path source, Path target, CopyOption... options) throws IOException;
public abstract void move (Path source,Path target, CopyOption... options) throws IOException;
public abstract InputStream newInputStream (Path path, OpenOption... options) throws IOException;
public abstract OutputStream newOutputStream (Path path, OpenOption... options) throws IOException;
public abstract SeekableByteChannel newByteChannel (Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException;
/*
public void createSymbolicLink (Path link, Path target) throws IOException {
throw new UnsupportedOperationException();
}
*/
public Path readSymbolicLink (Path link) throws IOException {
throw new UnsupportedOperationException();
}
public abstract <A extends BasicFileAttributes> A readAttributes (Path path, Class<A> type, LinkOption... options) throws IOException;
public abstract Map<String,Object> readAttributes (Path path, String attributes, LinkOption... options) throws IOException;
public abstract void setAttribute (Path path, String attribute, Object value, LinkOption... options) throws IOException;
public abstract <V extends FileAttributeView> V getFileAttributeView (Path path, Class<V> type, LinkOption... options);
public abstract DirectoryStream<Path> newDirectoryStream (Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException;
private static final class InstalledFileSystemProvidersHolder {
static final List<FileSystemProvider> providers = loadInstalledProviders();
private static List<FileSystemProvider> loadInstalledProviders () {
final List<FileSystemProvider> providers = new ArrayList<FileSystemProvider>();
providers.add(FileSystems.getDefault().provider());
for (final FileSystemProvider provider : ServiceLoader.load(FileSystemProvider.class, FileSystemProvider.class.getClassLoader())) {
if (!"file".equalsIgnoreCase(provider.getScheme()))
providers.add(provider);
}
return Collections.unmodifiableList(providers);
}
} // class InstalledFileSystemProvidersHolder
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.spi;
import com.llamalab.safs.Path;
import java.io.IOException;
/**
* System.setProperty("content.types.user.table", "/path/to/property/file");
*/
public abstract class FileTypeDetector {
protected FileTypeDetector () {}
public abstract String probeContentType (Path path) throws IOException;
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.unix;
import com.llamalab.safs.FileStore;
import com.llamalab.safs.FileSystem;
import com.llamalab.safs.LinkOption;
import com.llamalab.safs.Path;
import com.llamalab.safs.PathMatcher;
import com.llamalab.safs.WatchService;
import com.llamalab.safs.attribute.UserPrincipalLookupService;
import com.llamalab.safs.internal.BasicFileAttribute;
import com.llamalab.safs.internal.Utils;
import com.llamalab.safs.spi.FileSystemProvider;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.regex.Pattern;
public abstract class AbstractUnixFileSystem extends FileSystem {
protected final FileSystemProvider provider;
private volatile Path emptyDirectory;
private volatile Path rootDirectory;
public AbstractUnixFileSystem (FileSystemProvider provider) {
this.provider = provider;
}
@Override
public final FileSystemProvider provider () {
return provider;
}
@Override
public final Path getPath (String first, String... more) {
return getPathSanitized(UnixPath.sanitize(first, more));
}
public final Path getPath (String first) {
return getPathSanitized(UnixPath.sanitize(first, Utils.EMPTY_STRING_ARRAY));
}
protected Path getPathSanitized (String path) {
return new UnixPath(this, path);
}
@Override
public PathMatcher getPathMatcher (String syntaxAndPattern) {
final Pattern pattern;
if (syntaxAndPattern.startsWith("regex:"))
pattern = Pattern.compile(syntaxAndPattern.substring(6));
else if (syntaxAndPattern.startsWith("glob:"))
pattern = Pattern.compile(Utils.globToRegex(syntaxAndPattern, 5, syntaxAndPattern.length()));
else
throw new UnsupportedOperationException(syntaxAndPattern);
return new PathMatcher() {
@Override
public boolean matches (Path path) {
return pattern.matcher(path.toString()).matches();
}
};
}
protected abstract Path toRealPath (Path path, LinkOption... options) throws IOException;
@Override
public final String getSeparator() {
return "/";
}
@Override
public Set<String> supportedFileAttributeViews() {
return Collections.singleton(BasicFileAttribute.VIEW_NAME);
}
@Override
public Iterable<FileStore> getFileStores () {
return Collections.emptySet();
}
@Override
public final Iterable<Path> getRootDirectories () {
return Collections.singleton(getRootDirectory());
}
@Override
public UserPrincipalLookupService getUserPrincipalLookupService () {
throw new UnsupportedOperationException();
}
@Override
public WatchService newWatchService () throws IOException {
throw new UnsupportedOperationException();
}
public abstract Path getCurrentDirectory ();
public final Path getEmptyDirectory () {
if (emptyDirectory == null)
emptyDirectory = getPathSanitized("");
return emptyDirectory;
}
public final Path getRootDirectory () {
if (rootDirectory == null)
rootDirectory = getPathSanitized("/");
return rootDirectory;
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.unix;
import com.llamalab.safs.Path;
import com.llamalab.safs.internal.AbstractFileSystemProvider;
import com.llamalab.safs.internal.Utils;
import java.net.URI;
public abstract class AbstractUnixFileSystemProvider extends AbstractFileSystemProvider {
@Override
protected Class<? extends UnixPath> getPathType () {
return UnixPath.class;
}
@Override
public Path getPath (URI uri) {
if (!uri.isAbsolute() || uri.isOpaque() || uri.getAuthority() != null || uri.getFragment() != null || uri.getQuery() != null)
throw new IllegalArgumentException();
return new UnixPath((AbstractUnixFileSystem)getFileSystem(uri), UnixPath.sanitize(uri.getPath(), Utils.EMPTY_STRING_ARRAY));
}
}

View File

@ -0,0 +1,551 @@
/*
* Copyright (C) 2019 Henrik Lindqvist
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.llamalab.safs.unix;
import com.llamalab.safs.LinkOption;
import com.llamalab.safs.Path;
import com.llamalab.safs.ProviderMismatchException;
import com.llamalab.safs.WatchEvent;
import com.llamalab.safs.WatchKey;
import com.llamalab.safs.WatchService;
import com.llamalab.safs.internal.Utils;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class UnixPath implements Path {
private final AbstractUnixFileSystem fs;
private final String path;
private volatile short[] nameOffsets; // lazy
protected UnixPath (AbstractUnixFileSystem fs, String path) {
this.fs = fs;
this.path = path;
}
protected UnixPath (UnixPath other) {
this(other.fs, other.path);
this.nameOffsets = other.nameOffsets;
}
@Override
public final AbstractUnixFileSystem getFileSystem () {
return fs;
}
@Override
public final String toString () {
return path;
}
@Override
public final int hashCode () {
int hc = 17;
hc = 37*hc + fs.hashCode();
hc = 37*hc + path.hashCode();
return hc;
}
@Override
public boolean equals (Object obj) {
if (this == obj)
return true;
if (!(obj instanceof UnixPath))
return false;
final UnixPath other = (UnixPath)obj;
return fs.equals(other.fs)
&& path.equals(other.path);
}
@SuppressWarnings("NullableProblems")
@Override
public int compareTo (Path other) {
return path.compareTo(other.toString());
}
@Override
public final boolean isAbsolute () {
return path.startsWith("/");
}
public final boolean isHidden () {
return path.startsWith(".");
}
public final boolean isEmpty () {
return path.isEmpty();
}
public final boolean isRoot () {
return path.equals("/");
}
/**
* @return true if . or ..
*/
public final boolean isDots () {
return ".".equals(path) || "..".equals(path);
}
private static final short[] ZERO_NAME_OFFSETS = new short[] { 0 };
private static final short[] EMPTY_NAME_OFFSETS = new short[0];
private short[] getNameOffsets () {
if (nameOffsets != null)
return nameOffsets;
final String path = this.path;
final int length = path.length();
if (length == 0)
return nameOffsets = ZERO_NAME_OFFSETS;
int count = 0;
int index = 0;
while (index < length) {
if ('/' != path.charAt(index)) {
if (index++ > 0xFFFF)
throw new ArrayStoreException();
++count;
while (index < length && '/' != path.charAt(index))
++index;
}
else
++index;
}
if (count == 0)
return nameOffsets = EMPTY_NAME_OFFSETS;
final short[] offsets = new short[count];
index = 0;
count = 0;
while (index < length) {
if ('/' != path.charAt(index)) {
offsets[count++] = (short)index++;
while (index < length && '/' != path.charAt(index))
++index;
}
else
++index;
}
return nameOffsets = offsets;
}
@Override
public Path getRoot () {
return isAbsolute() ? fs.getRootDirectory() : null;
}
@Override
public Path getParent () {
final short[] offsets = getNameOffsets();
final int count = offsets.length;
if (count == 0)
return null;
final int end = (offsets[count - 1] & 0xFFFF) - 1;
if (end <= 0)
return getRoot();
return fs.getPathSanitized(path.substring(0, end));
}
/*
public final Path getAncestor (int endIndex) {
final short[] offsets = getNameOffsets();
final int count = offsets.length;
if (endIndex < 0 || count < endIndex)
throw new IllegalArgumentException();
if (count == 0)
return null;
if (endIndex == count)
return this;
final int end = (offsets[endIndex] & 0xFFFF) - 1;
if (end <= 0)
return getRoot();
return fs.getPathSanitized(path.substring(0, end));
}
*/
@Override
public Path getFileName () {
final short[] offsets = getNameOffsets();
final int count = offsets.length;
if (count == 0)
return null;
if (count == 1 && path.length() != 0 && '/' != path.charAt(0))
return this;
return fs.getPathSanitized(path.substring(offsets[count - 1] & 0xFFFF));
}
public final int getNameCount () {
return getNameOffsets().length;
}
public Path getName (int index) {
return subpath(index, index + 1);
}
public Path subpath (int beginIndex, int endIndex) {
final short[] offsets = getNameOffsets();
final int count = offsets.length;
if (beginIndex < 0 || count < endIndex || endIndex <= beginIndex)
throw new IllegalArgumentException();
if (endIndex < count)
return fs.getPathSanitized(path.substring(offsets[beginIndex] & 0xFFFF, (offsets[endIndex] & 0xFFFF) - 1));
else
return fs.getPathSanitized(path.substring(offsets[beginIndex] & 0xFFFF));
}
@Override
public final boolean startsWith (Path other) {
return fs.equals(other.getFileSystem())
&& startsWithSanitized(((UnixPath)other).path);
}
@Override
public final boolean startsWith (String other) {
return startsWithSanitized(sanitize(other, Utils.EMPTY_STRING_ARRAY));
}
private boolean startsWithSanitized (String other) {
if (!path.startsWith(other))
return false;
final int end = other.length();
return end == path.length() || '/' == path.charAt(end) || "/".equals(other);
}
public final boolean endsWith (Path other) {
return fs.equals(other.getFileSystem())
&& endsWithSanitized(((UnixPath)other).path);
}
public final boolean endsWith (String other) {
return endsWithSanitized(sanitize(other, Utils.EMPTY_STRING_ARRAY));
}
private boolean endsWithSanitized (String other) {
if (!path.endsWith(other))
return false;
final int start = path.length() - other.length();
return start == 0 || '/' == path.charAt(start - 1);
}
@SuppressWarnings("NullableProblems")
@Override
public Iterator<Path> iterator () {
final short[] offsets = getNameOffsets();
final int count = offsets.length;
if (count == 0)
return Utils.emptyIterator();
return new NameIterator<Path>(path, offsets, count) {
@Override
protected Path next (String path, int start, int end) {
return fs.getPathSanitized(path.substring(start, end));
}
};
}
public final Iterator<CharSequence> charSequenceIterator () {
final short[] offsets = getNameOffsets();
final int count = offsets.length;
if (count == 0)
return Utils.emptyIterator();
return new NameIterator<CharSequence>(path, offsets, count) {
@Override
protected CharSequence next (String path, int start, int end) {
return path.subSequence(start, end);
}
};
}
public final Iterator<String> stringIterator () {
final short[] offsets = getNameOffsets();
final int count = offsets.length;
if (count == 0)
return Utils.emptyIterator();
return new NameIterator<String>(path, offsets, count) {
@Override
protected String next (String path, int start, int end) {
return path.substring(start, end);
}
};
}
// FIXME: horrid
@Override
public Path normalize () {
CharSequence[] names = Utils.EMPTY_CHAR_SEQUENCE_ARRAY;
int end = 0;
for (Iterator<CharSequence> i = charSequenceIterator(); i.hasNext();) {
final CharSequence n = i.next();
if ("..".contentEquals(n) && end > 0)
--end;
else if (!".".contentEquals(n)){
if (names.length == end)
names = Arrays.copyOf(names, Math.max(end*2, 8));
names[end++] = n;
}
}
if (end == 0)
return isAbsolute() ? fs.getRootDirectory() : fs.getEmptyDirectory();
return fs.getPath(join(names, 0, end, isAbsolute() ? "/" : ""));
}
// FIXME: horrid
@Override
public Path relativize (Path other) {
checkPath(other);
if (isAbsolute() != other.isAbsolute())
throw new IllegalArgumentException("Absolute vs relative");
//if (equals(that))
// return fs.getEmptyDirectory();
final Iterator<CharSequence> bi = charSequenceIterator();
final Iterator<CharSequence> ci = ((UnixPath)other).charSequenceIterator();
final RelativizeHelper h = new RelativizeHelper();
CharSequence bn, cn = null;
out: while (bi.hasNext() && ci.hasNext()) {
while (".".contentEquals(bn = bi.next()))
if (!bi.hasNext()) break out;
while (".".contentEquals(cn = ci.next()))
if (!ci.hasNext()) break out;
if (!Utils.contentEquals(bn, cn)) {
h.base(bn);
break;
}
h.add(bn);
++h.start;
cn = null;
}
while (bi.hasNext())
if (!".".contentEquals(bn = bi.next())) h.base(bn);
if (cn != null && !".".contentEquals(cn))
h.child(cn);
while (ci.hasNext())
if (!".".contentEquals(cn = ci.next())) h.child(cn);
return fs.getPathSanitized(join(h.names, h.start, h.end, ""));
}
private static final class RelativizeHelper {
public CharSequence[] names = new CharSequence[8];
public int start = 0, end = 0;
public void base (CharSequence value) {
if ("..".contentEquals(value) && start > 0)
--start;
else
add("..");
}
public void child (CharSequence value) {
if ("..".contentEquals(value) && start < end && !"..".contentEquals(names[end - 1]))
--end;
else
add(value);
}
public void add (CharSequence value) {
if (names.length == end)
names = Arrays.copyOf(names, end*2);
names[end++] = value;
}
} // class RelativizeHelper
@Override
public Path resolve (Path other) {
checkPath(other);
if (other.isAbsolute())
return other;
if (((UnixPath)other).isEmpty())
return this;
return fs.getPath(path, ((UnixPath)other).path);
}
@Override
public Path resolve (String other) {
if (other.startsWith("/"))
return fs.getPath(other);
if (other.isEmpty())
return this;
return fs.getPath(path, other);
}
public Path resolveSibling (Path other) {
checkPath(other);
final Path parent = getParent();
return (parent != null) ? parent.resolve(other) : other;
}
public Path resolveSibling (String other) {
final Path parent = getParent();
return (parent != null) ? parent.resolve(other) : fs.getPath(other);
}
@Override
public Path toAbsolutePath () {
return isAbsolute() ? this : fs.getCurrentDirectory().resolve(this);
}
@Override
public Path toRealPath (LinkOption... options) throws IOException {
return fs.toRealPath(this, options);
}
@Override
public File toFile () {
return new File(path);
}
@Override
public URI toUri () {
try {
return new URI(fs.provider().getScheme(), toAbsolutePath().toString(), null);
}
catch (Throwable t) {
throw new IllegalStateException(t); // shouldn't happen
}
}
private static final WatchEvent.Modifier[] EMPTY_MODIFIER_ARRAY = new WatchEvent.Modifier[0];
@Override
public final WatchKey register (WatchService service, WatchEvent.Kind<?>... kinds) throws IOException {
return register(service, kinds, EMPTY_MODIFIER_ARRAY);
}
@Override
public WatchKey register (WatchService service, WatchEvent.Kind<?>[] kinds, WatchEvent.Modifier... modifiers) throws IOException {
throw new UnsupportedOperationException();
}
private static void checkPath (Path path) {
if (!(path instanceof UnixPath))
throw (path == null) ? new NullPointerException() : new ProviderMismatchException();
}
/*
private static String sanitize (String path) {
return sanitize(path, Utils.EMPTY_STRING_ARRAY);
}
*/
/**
* Removes double and tail slash
*/
static String sanitize (String first, String[] more) {
CharSequence parent = "";
for (int i = -1, count = more.length;;) {
parent = sanitizeChunk(parent, first);
if (++i == count)
break;
first = more[i];
}
return parent.toString();
}
private static CharSequence sanitizeChunk (CharSequence parent, String path) {
int end = path.length();
if (end == 0)
return parent; // empty
while (end > 0 && '/' == path.charAt(end - 1)) --end;
if (end == 0)
return (parent.length() == 0) ? "/" : parent; // root
// At this point it's neither empty nor root.
int start = 0;
while (start < end && '/' == path.charAt(start)) ++start;
final int parentLength = parent.length();
int index = path.indexOf("//", start + 1);
if (index == -1 || index >= end) {
// no //
if (parentLength == 0) {
if (start > 0)
--start; // keep initial /
return path.substring(start, end);
}
if (parentLength == 1 && '/' == parent.charAt(0)) {
// root parent
if (start > 0)
return path.substring(start - 1, end);
else
return new StringBuilder("/").append(path, start, end);
}
final StringBuilder sb = (parent instanceof StringBuilder) ? (StringBuilder)parent : new StringBuilder(parent);
return sb.append('/').append(path, start, end);
}
// found //
final StringBuilder sb = (parent instanceof StringBuilder) ? (StringBuilder)parent : new StringBuilder(parent);
if (parentLength == 0) {
if (start > 0)
--start; // keep initial /
}
else if (parentLength != 1 || '/' != parent.charAt(0)) {
// not root parent
sb.append('/');
}
while (++index < end) {
sb.append(path, start, index);
while (index < end && '/' == path.charAt(index)) ++index;
start = index;
}
return sb.append(path, start, index);
}
private static String join (CharSequence[] cs, int start, int end, String prefix) {
final StringBuilder sb = new StringBuilder();
while (start < end) {
sb.append(prefix).append(cs[start++]);
prefix = "/";
}
return sb.toString();
}
private static abstract class NameIterator<T> implements Iterator<T> {
private final String path;
private final short[] offsets;
private final int end;
private int index;
public NameIterator (String path, short[] offsets, int count) {
this.path = path;
this.offsets = offsets;
this.end = count - 1;
}
@Override
public final boolean hasNext () {
return index <= end;
}
@Override
public final T next () {
final int i = index;
if (i > end)
throw new NoSuchElementException();
++index;
return next(
path,
offsets[i] & 0xFFFF,
(i < end) ? (offsets[i + 1] & 0xFFFF) - 1 : path.length());
}
@Override
public final void remove () {
throw new UnsupportedOperationException();
}
protected abstract T next (String path, int start, int end);
} // class NameIterator
}

View File

@ -17,7 +17,7 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import java.io.File
import java.nio.file.Path
import com.llamalab.safs.Path
/**
* [数据对象][PluginData] 存储仓库.

View File

@ -13,12 +13,13 @@ import net.mamoe.mirai.utils.SecretsProtection
import net.mamoe.mirai.utils.lateinitMutableProperty
import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import java.nio.file.Path
import com.llamalab.safs.Path
import java.util.*
import kotlin.io.path.createDirectories
import kotlin.io.path.isRegularFile
import kotlin.io.path.readBytes
import kotlin.io.path.writeBytes
import com.llamalab.safs.kotlin.io.path.createDirectories
import com.llamalab.safs.kotlin.io.path.readBytes
import com.llamalab.safs.kotlin.io.path.writeBytes
import com.llamalab.safs.kotlin.io.path.isRegularFile
internal class ConsoleSecretsCalculator(
private val file: Path,

View File

@ -24,7 +24,7 @@ import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.currentTimeMillis
import net.mamoe.yamlkt.Yaml
import java.io.File
import java.nio.file.Path
import com.llamalab.safs.Path
@Suppress("RedundantVisibilityModifier") // might be public in the future
internal open class MultiFilePluginDataStorageImpl(

View File

@ -31,7 +31,7 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.*
import net.mamoe.yamlkt.Yaml
import java.io.File
import java.nio.file.Path
import com.llamalab.safs.Path
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.CoroutineContext

View File

@ -41,7 +41,7 @@ import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.safeCast
import java.io.File
import java.io.InputStream
import java.nio.file.Path
import com.llamalab.safs.Path
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.coroutines.CoroutineContext

View File

@ -19,7 +19,7 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
import java.io.InputStream
import java.nio.file.Path
import com.llamalab.safs.Path
import kotlin.coroutines.CoroutineContext
internal abstract class NotYetLoadedJvmPlugin(

View File

@ -30,7 +30,7 @@ import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.*
import java.io.File
import java.nio.file.Path
import com.llamalab.safs.Path
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.coroutines.CoroutineContext

View File

@ -24,7 +24,7 @@ import java.io.FileOutputStream
import java.io.PrintStream
import java.lang.management.ManagementFactory
import java.lang.reflect.Method
import java.nio.file.Paths
import com.llamalab.safs.Paths
import java.time.Instant
import java.time.ZoneOffset
import java.util.*
@ -33,7 +33,7 @@ import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.io.path.writeText
import com.llamalab.safs.kotlin.io.path.writeText
internal object ShutdownDaemon {
@Suppress("RemoveRedundantQualifierName")
@ -110,6 +110,7 @@ internal object ShutdownDaemon {
bridge.mainLogger.debug { "SHUTDOWN DAEMON STARTED........." }
}
@OptIn(ConsoleInternalApi::class)
@Suppress("MemberVisibilityCanBePrivate")
fun dumpCrashReport(saveError: Boolean) {
val isAndroidSystem = kotlin.runCatching { Class.forName("android.util.Log") }.isSuccess

View File

@ -13,7 +13,7 @@ import net.mamoe.mirai.console.data.PluginConfig
import net.mamoe.mirai.console.data.PluginData
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import java.io.File
import java.nio.file.Path
import com.llamalab.safs.Path
/**

View File

@ -17,7 +17,7 @@ import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.utils.NotStableForInheritance
import java.io.File
import java.nio.file.Path
import com.llamalab.safs.Path
/**
* 插件管理器.