/*license*\
XBN-Java: Copyright (C) 2014, Jeff Epstein (aliteralmind __DASH__ github __AT__ yahoo __DOT__ com)
This software is dual-licensed under the:
- Lesser General Public License (LGPL) version 3.0 or, at your option, any later version;
- Apache Software License (ASL) version 2.0.
Either license may be applied at your discretion. More information may be found at
- http://en.wikipedia.org/wiki/Multi-licensing.
The text of both licenses is available in the root directory of this project, under the names "LICENSE_lgpl-3.0.txt" and "LICENSE_asl-2.0.txt". The latest copies may be downloaded at:
- LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
- ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
\*license*/
package com.github.xbn.io;
import java.util.Objects;
import com.github.xbn.lang.CrashIfObject;
import java.nio.file.Path;
import java.nio.file.AccessDeniedException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.LinkOption;
import java.io.IOException;
import java.nio.file.Paths;
/**
<p>If a {@link java.nio.file.Path} is bad, crash. Otherwise, do nothing.</p>
* @since 0.1.0
* @author Copyright (C) 2014, Jeff Epstein ({@code aliteralmind __DASH__ github __AT__ yahoo __DOT__ com}), dual-licensed under the LGPL (version 3.0 or later) or the ASL (version 2.0). See source code for details. <a href="http://xbnjava.aliteralmind.com">{@code http://xbnjava.aliteralmind.com}</a>, <a href="https://github.com/aliteralmind/xbnjava">{@code https://github.com/aliteralmind/xbnjava}</a>
**/
public class PathMustBe {
private Existence existing;
private Readable readable ;
private Writable writable ;
private FileDirectory fileDir ;
private LinkOption[] linkOpts ;
/**
<p>Create a new instance that has no restrictions.</p>
<p>This calls<ol>
<li>{@link #existing(Existence) existing}<code>({@link Existence}.{@link Existence#OPTIONAL OPTIONAL})</code></li>
<li>{@link #readable(Readable) readable}<code>({@link Readable}.{@link Readable#OPTIONAL OPTIONAL})</code></li>
<li>{@link #writable(Writable) writable}<code>({@link Writable}.{@link Writable#OPTIONAL OPTIONAL})</code></li>
<li>{@link #fileOrDirectory(FileDirectory) fileOrDirectory}<code>({@link FileDirectory}.{@link FileDirectory#EITHER EITHER})</code></li>
<li>{@link #linkOptions(LinkOption...)}(new {@link java.nio.file.LinkOption}[]{});
</ol></p>
*/
public PathMustBe() {
existing(Existence.OPTIONAL);
readable(Readable.OPTIONAL);
writable(Writable.OPTIONAL);
fileOrDirectory(FileDirectory.EITHER);
linkOptions(new LinkOption[]{});
}
/**
<p>Is existing required?.</p>
* @see #PathMustBe()
* @see #existing() existing
*/
public Existence getExistence() {
return existing;
}
/**
<p>Is readable required?.</p>
* @see #PathMustBe()
* @see #readable() readable
*/
public Readable getReadable() {
return readable;
}
/**
<p>Is writablity required?.</p>
* @see #PathMustBe() PathMustBe
* @see #writable() writable
*/
public Writable getWritable() {
return writable;
}
/**
<p>Must the path be a regular-file or a directory?.</p>
* @see #PathMustBe() PathMustBe
* @see #directory() directory
* @see #regularFile() regularFile
*/
public FileDirectory getFileDirectory() {
return fileDir;
}
/**
<p>Should symbolic links be followed?.</p>
* @return A <b><i>mutable</i></b> array of all link options.
* @see #PathMustBe() PathMustBe
* @see #existing() existing
* @see #regularFile() regularFile
* @see #directory() directory
*/
public LinkOption[] getLinkOptions() {
return linkOpts;
}
/**
<p>Must exist.</p>
* @return <code>{@link #existing(Existence) existing}({@link Existence}.{@link Existence#REQUIRED REQUIRED})</code>
*/
public PathMustBe existing() {
return existing(Existence.REQUIRED);
}
/**
<p>Declare if existing is required.</p>
* @param existing May not be {@code null}. Get with {@link #getExistence() getExistence}{@code ()}
* @return <i>{@code this}</i>
* @see #existing()
* @see #linkOptions(LinkOption...) linkOptions
*/
public PathMustBe existing(Existence existing) {
Objects.requireNonNull(existing, "existing");
this.existing = existing;
return this;
}
/**
<p>Must be readable.</p>
* @return <code>{@link #readable(Readable) readable}({@link Readable}.{@link Readable#REQUIRED REQUIRED})</code>
*/
public PathMustBe readable() {
return readable(Readable.REQUIRED);
}
/**
<p>Declare if readable is required.</p>
* @param readable May not be {@code null}. Get with {@link #getReadable() getReadable}{@code ()}
* @return <i>{@code this}</i>
* @see #readable()
* @see #linkOptions(LinkOption...) linkOptions
*/
public PathMustBe readable(Readable readable) {
Objects.requireNonNull(readable, "readable");
this.readable = readable;
return this;
}
/**
<p>Must be writable.</p>
* @return <code>{@link #writable(Writable) writable}({@link Writable}.{@link Writable#REQUIRED REQUIRED})</code>
*/
public PathMustBe writable() {
return writable(Writable.REQUIRED);
}
/**
<p>Declare if writable is required.</p>
* @param writable May not be {@code null}. Get with {@link #getWritable() getWritable}{@code ()}.
* @return <i>{@code this}</i>
* @see #writable()
* @see #linkOptions(LinkOption...) linkOptions
*/
public PathMustBe writable(Writable writable) {
Objects.requireNonNull(writable, "writable");
this.writable = writable;
return this;
}
/**
<p>Should symbolic links be followed?. Used when testing for {@linkplain #existing() existing} and is-a-{@linkplain #regularFile() file}-or-{@linkplain #directory() directory}.</p>
* @param link_opts Options indicating how symbolic links are handled. Get with {@link #getLinkOptions() getLinkOptions}{@code ()}
* @return <i>{@code this}</i>
* @see #noFollowLinks()
*/
public PathMustBe linkOptions(LinkOption... link_opts) {
linkOpts = link_opts;
return this;
}
/**
<p>Do not follow symbolic links.</p>
* @return <code>{@link #linkOptions(LinkOption...) linkOptions}({@link java.nio.file.LinkOption}.{@link java.nio.file.LinkOption#NOFOLLOW_LINKS NOFOLLOW_LINKS})</code>
*/
public PathMustBe noFollowLinks() {
return linkOptions(LinkOption.NOFOLLOW_LINKS);
}
/**
<p>Must be a directory.</p>
* @return <code>{@link #fileOrDirectory(FileDirectory) fileOrDirectory}({@link FileDirectory}.{@link FileDirectory#DIRECTORY DIRECTORY})</code>
*/
public PathMustBe directory() {
return fileOrDirectory(FileDirectory.DIRECTORY);
}
/**
<p>Must be a regular file.</p>
* @return <code>{@link #fileOrDirectory(FileDirectory) fileOrDirectory}({@link FileDirectory}.{@link FileDirectory#REGULAR_FILE REGULAR_FILE})</code>
*/
public PathMustBe regularFile() {
return fileOrDirectory(FileDirectory.REGULAR_FILE);
}
/**
<p>Declare if the path must be a regular file or a directory.</p>
* @param file_dir May not be {@code null}. Get with {@link #getFileDirectory() getFileDirectory}{@code ()}.
* @return <i>{@code this}</i>
* @see #linkOptions(LinkOption...) linkOptions
* @see #directory()
* @see #regularFile()
*/
public PathMustBe fileOrDirectory(FileDirectory file_dir) {
Objects.requireNonNull(file_dir, "file_dir");
fileDir = file_dir;
return this;
}
/**
<p>If a path is invalid, crash (with runtime errors only). Otherwise, do nothing.</p>
* <p>Equal to
<br/> <code>{@link #crashIfBadX(Path, String) crashIfBadX}(path, path_varName)</code></p>
*/
public void crashIfBad(Path path, String path_varName) {
try {
crashIfBadX(path, path_varName);
} catch(IOException iox) {
throw new RTIOException(iox);
}
}
/**
<p>If a path is valid, get it. Otherwise, crash (with runtime errors only).</p>
* @return <code>{@link #getOrCrashIfBadX(String, String) getOrCrashIfBadX}(path, path_varName)</code></p>
*/
public Path getOrCrashIfBad(String path, String path_varName) {
try {
return getOrCrashIfBadX(path, path_varName);
} catch(NoSuchFileException nsfx) {
throw new RTNoSuchFileException(nsfx);
} catch(AccessDeniedException adx) {
throw new RTAccessDeniedException(adx);
}
}
/**
<p>If a path is valid, get it. Otherwise, crash.</p>
<p>This<ol>
<li>Calls <code>crashIfBadX({@link java.nio.file.Paths}.{@link java.nio.file.Paths#get(String, String...) get}(path), path_varName)</code></li>
<li>And returns the path.</li>
</ol></p>
*/
public Path getOrCrashIfBadX(String path, String path_varName) throws NoSuchFileException, AccessDeniedException {
Path ppath = Paths.get(path);
crashIfBadX(ppath, path_varName);
return ppath;
}
/**
<p>If a path is invalid, crash. Otherwise, do nothing.</p>
* <p>Equal to
<br/> <code>{@link #crashIfBadX(Path, String, Existence, Readable, Writable, FileDirectory, LinkOption...) crashIfBadX}(path, path_varName, existing_is, readable_is, writable_is, file_directory, link_opts)</code></p>
*/
public static final void crashIfBad(Path path, String path_varName, Existence existing_is, Readable readable_is, Writable writable_is, FileDirectory file_directory, LinkOption... link_opts) {
try {
crashIfBadX(path, path_varName, existing_is, readable_is, writable_is, file_directory, link_opts);
} catch(NoSuchFileException nsfx) {
throw new RTNoSuchFileException(nsfx);
} catch(AccessDeniedException adx) {
throw new RTAccessDeniedException(adx);
}
}
//Originates with crashIfBadX, needed by isGood...START
/**
<p>If a path is invalid, crash. Otherwise, do nothing.</p>
* <p>Equal to
<br/> <code>PathMustBe.{@link #crashIfBadX(Path, String, Existence, Readable, Writable, FileDirectory, LinkOption...) crashIfBadX}(path, path_varName, {@link #getExistence() getExistence}(), {@link #getReadable() getReadable}(), {@link #getWritable() getWritable}(), {@link #getFileDirectory() getFileDirectory}(), ({@link java.nio.file.LinkOption}[]){@link #getLinkOptions() getLinkOptions}())</code></p>
*/
public void crashIfBadX(Path path, String path_varName) throws NoSuchFileException, AccessDeniedException {
PathMustBe.crashIfBadX(path, path_varName, getExistence(), getReadable(), getWritable(), getFileDirectory(), (LinkOption[])getLinkOptions());
}
/**
<p>If a path is invalid, crash. Otherwise, do nothing.</p>
* @param path May not be {@code null}.
* @param path_varName Descriptive name for {@code path}. <i>Should</i> not be {@code null} or empty.
* @param existing_is May not be {@code null}.
* @param readable_is May not be {@code null}.
* @param writable_is May not be {@code null}
* @param file_directory May not be {@code null}.
* @param link_opts Options indicating how symbolic links are handled.
* @see #isGood(Path, Existence, Readable, Writable, FileDirectory, LinkOption...) isGood
* @see #crashIfBad(Path, String) crashIfBad(p,s)
* @see #getOrCrashIfBad(String, String) crashIfBad(s,s)
* @see #crashIfBadX(Path, String) crashIfBad(p,s)
* @see #getOrCrashIfBadX(String, String) crashIfBad(s,s)
* @see #crashIfBad(Path, String, Existence, Readable, Writable, FileDirectory, LinkOption...) crashIfBad(Path, String, Existence, ...)
*/
public static final void crashIfBadX(Path path, String path_varName, Existence existing_is, Readable readable_is, Writable writable_is, FileDirectory file_directory, LinkOption... link_opts) throws NoSuchFileException, AccessDeniedException {
try {
if(Files.exists(path, link_opts)) {
if(!Files.isWritable(path) && writable_is.isRequired()) {
throw new AccessDeniedException("Exists, but not writable: " + path_varName + "=" + path);
}
} else if(existing_is.isRequired()) {
throw new NoSuchFileException(path.toString());
} else {
Path parent = path.getParent();
if(parent == null || !Files.isWritable(parent) && writable_is.isRequired()) {
throw new AccessDeniedException("Does not exist, and its directory is not writable: " + path_varName + "=" + path);
}
}
if(!Files.isReadable(path) && readable_is.isRequired()) {
throw new AccessDeniedException("Not readable: " + path_varName + "=" + path);
}
if(file_directory.isDirectory()) {
if(!Files.isDirectory(path, link_opts)) {
throw new IllegalArgumentException("Not a directory: " + path_varName + "=" + path);
}
} else if(file_directory.isRegularFile() && !Files.isRegularFile(path, link_opts)) {
throw new IllegalArgumentException("Not a regular file: " + path_varName + "=" + path);
}
} catch(RuntimeException rx) {
CrashIfObject.nnull(path, "path", null);
CrashIfObject.nnull(existing_is, "existing_is", null);
CrashIfObject.nnull(writable_is, "writable_is", null);
CrashIfObject.nnull(readable_is, "readable_is", null);
throw CrashIfObject.nullOrReturnCause(file_directory, "file_directory", null, rx);
}
}
//Originates with crashIfBadX, needed by isGood...END
//Originates with crashIfBadX, needed by isGood...START
/**
<p>Does a path conform to all its restrictions?.</p>
* <p>Equal to
<br/> <code>PathMustBe.{@link #isGood(Path, Existence, Readable, Writable, FileDirectory, LinkOption...) isGood}(path, {@link #getExistence() getExistence}(), {@link #getReadable() getReadable}(), {@link #getWritable() getWritable}(), {@link #getFileDirectory() getFileDirectory}(), ({@link java.nio.file.LinkOption}[]){@link #getLinkOptions() getLinkOptions}())</code></p>
*/
public boolean isGood(Path path) {
return PathMustBe.isGood(path, getExistence(), getReadable(), getWritable(), getFileDirectory(), (LinkOption[])getLinkOptions());
}
/**
<p>Does a path conform to all its restrictions?.</p>
* @param path May not be {@code null}.
* @param existing_is May not be {@code null}.
* @param readable_is May not be {@code null}.
* @param writable_is May not be {@code null}
* @param file_directory May not be {@code null}.
* @param link_opts Options indicating how symbolic links are handled.
* @see #isGood(Path)
* @see #crashIfBadX(Path, String, Existence, Readable, Writable, FileDirectory, LinkOption...) crashIfBadX
*/
public static final boolean isGood(Path path, Existence existing_is, Readable readable_is, Writable writable_is, FileDirectory file_directory, LinkOption... link_opts) {
try {
if(Files.exists(path, link_opts)) {
if(!Files.isWritable(path) && writable_is.isRequired()) {
return false;
}
} else if(existing_is.isRequired()) {
return false;
} else {
Path parent = path.getParent();
if(parent == null || !Files.isWritable(parent) && writable_is.isRequired()) {
return false;
}
}
if(!Files.isReadable(path) && readable_is.isRequired()) {
return false;
}
if(file_directory.isDirectory()) {
if(!Files.isDirectory(path, link_opts)) {
return false;
}
} else if(file_directory.isRegularFile() && !Files.isRegularFile(path, link_opts)) {
return false;
}
return true;
} catch(RuntimeException rx) {
CrashIfObject.nnull(path, "path", null);
CrashIfObject.nnull(existing_is, "existing_is", null);
CrashIfObject.nnull(writable_is, "writable_is", null);
CrashIfObject.nnull(readable_is, "readable_is", null);
throw CrashIfObject.nullOrReturnCause(file_directory, "file_directory", null, rx);
}
}
//Originates with crashIfBadX, needed by isGood...END
}