All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.truezip.file.TFile Maven / Gradle / Ivy

Go to download

This module provides the TFile* classes for simple, uniform, transparent, thread-safe, read/write access to archive files as if they were virtual directories in a file system path. This module also provides Swing GUI classes for viewing file trees and choosing entries in archive files.

There is a newer version: 7.7.10
Show newest version
/*
 * Copyright (C) 2004-2011 Schlichtherle IT Services
 *
 * 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 de.schlichtherle.truezip.file;

import de.schlichtherle.truezip.util.UriBuilder;
import de.schlichtherle.truezip.fs.FsManager;
import de.schlichtherle.truezip.fs.sl.FsManagerLocator;
import de.schlichtherle.truezip.fs.FsSyncWarningException;
import de.schlichtherle.truezip.io.Paths.Splitter;
import de.schlichtherle.truezip.io.Paths;
import de.schlichtherle.truezip.fs.FsController;
import de.schlichtherle.truezip.fs.FsEntryName;
import de.schlichtherle.truezip.fs.FsScheme;
import de.schlichtherle.truezip.fs.FsPath;
import de.schlichtherle.truezip.fs.FsMountPoint;
import de.schlichtherle.truezip.io.Streams;
import de.schlichtherle.truezip.fs.FsEntry;
import de.schlichtherle.truezip.fs.FsFilteringManager;
import de.schlichtherle.truezip.fs.FsSyncException;
import de.schlichtherle.truezip.fs.FsSyncOption;
import de.schlichtherle.truezip.util.BitField;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.Icon;
import javax.swing.filechooser.FileSystemView;
import net.jcip.annotations.Immutable;

import static de.schlichtherle.truezip.fs.FsEntry.*;
import static de.schlichtherle.truezip.fs.FsEntryName.*;
import static de.schlichtherle.truezip.fs.FsSyncOption.*;
import static de.schlichtherle.truezip.fs.FsSyncOptions.*;
import static de.schlichtherle.truezip.fs.FsUriModifier.*;
import static de.schlichtherle.truezip.entry.Entry.Size.*;
import static de.schlichtherle.truezip.entry.Entry.Type.*;
import static de.schlichtherle.truezip.fs.FsOutputOption.*;

/**
 * A replacement for the class {@link File} which provides transparent
 * read/write access to archive files and their entries as if they were
 * (virtual) directories and files.
 * Because this class actually extends the class {@link File} it can be used
 * with the class {@link FileSystemView} or any other classes which depend on
 * the class {@link File}.
 *
 * 

Bulk I/O Methods

*

* This class provides some convenient methods which use pooled buffers and * pooled threads in order to achieve superior performance as compared to the * naive read-then-write-in-a-loop approach. * These bulk I/O methods fall into the following categories: *

    *
  1. The {@code cp(_p|_r|_rp)?} methods copy files or directory trees. * The method names have been modeled after the Unix command line utility * {@code cp} with its options. *
  2. The {@code mv} methods move files or directory trees. * The method names have been modeled after the Unix command line utility * {@code mv}. *
  3. The {@code rm(_r)?} methods remove files or directory trees. * The method names have been modeled after the Unix command line utility * {@code rm} with its options. *
  4. The {@code input|output} methods copy the given streams to this file or * vice versa. * In contrast to the previous methods they never close their argument * streams, so applications can call them multiple times on the same * streams to concatenate data. *
  5. Finally, the {@link #cat(InputStream, OutputStream)} * method is the core copy engine for all these methods. * It performs the data transfer from an input stream to an output stream. * When used with unbuffered input and output stream * implementations, it delivers the same performance as the transfer * method in the package {@code java.nio}. * Its name is modeled after the Unix command line utility {@code cat}. *
* Important: You must provide the full path name for both * source and destination parameters to any of these methods! * In particular, both the source and destination parameters must either * represent a file or a directory - mixing file and directory parameters will * not work. * This limitation is designed to prevent ambiguous method semantics. * *

Direct Data Copying (DDC)

*

* If data is copied from an archive file to another archive file of the * same type, some of the copy methods support a feature called Direct Data * Copying (DDC) to achieve best performance: * DDC copies the raw data from the source archive entry to the destination * archive entry without the need to temporarily reproduce, copy and process * the original data again. *

* The benefits of this feature are archive driver specific: * In case of ZIP compatible files with compressed entries, this avoids the * need to inflate the data from the source entry just to deflate it again for * the destination entry. * In case of TAR compatible files, it avoids the need to create an * additional temporary file, but shows no impact otherwise - the TAR file * format doesn't support compression. * *

Traversing Directory Trees

*

* When traversing directory trees using different instances of this class, * it's important that they use {@link TArchiveDetector}s which recognize the * same set of archive files, i.e. their {@link TArchiveDetector#toString()} * method compares {@link String#equals(Object) equal}. * This is required in order to make sure that the cached data structures * in the TrueZIP Kernel do not get bypassed. * Otherwise, archive files may loose data or even get corrupted! *

* When copying or moving a directory tree, if you need a verbatim copy of any * archive files within this directory tree, then make sure that any cached * data structures in the TrueZIP Kernel are purged by calling {@link #umount()} * before calling one of the methods which accept the additional parameter * {@link TArchiveDetector#NULL} for the traversal of this directory tree. * For example, to make a recursive archive copy, call *


 * TFile src = ...
 * TFile dst = ...
 * TFile.umount(); // commit changes and purge any cached data
 * TFile.cp_rp(src, dst, TArchiveDetector.NULL, TArchiveDetector.NULL);
 * 
* *

Detecting Archive Files and False Positives

*

* Whenever an archive file suffix is detected in a path, this class treats * the corresponding file or directory as a prospective archive file. * The word "prospective" suggests that just because a file is named * archive.zip it isn't necessarily a valid ZIP file. * In fact, it could be anything, even a plain directory in the OS file system! *

* Such an invalid archive file is called a false positive archive file. * TrueZIP correctly identifies all types of false positive archive files by * performing a recursive look up operation for the first parent file system * where the given archive file actually exists and treats it according to the * true state of this entity. *

* The following table shows how certain methods in this class behave, * depending upon an archive file's path and its true state in the * first parent file system where it actually exists. *

*

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
PathTrue State{@code isArchive()}1{@code isDirectory()}{@code isFile()}{@code exists()}{@code length()}2
archive.zip3Valid ZIP file{@code true}{@code true}{@code false}{@code true}{@code 0}
archive.zipFalse positive: Plain directory{@code true}{@code true}{@code false}{@code true}{@code ?}
archive.zipFalse positive: Plain file{@code true}{@code false}{@code true}{@code true}{@code ?}
archive.zipFalse positive: Regular special file{@code true}{@code false}{@code false}{@code true}{@code ?}
archive.zipTFile or directory does not exist{@code true}{@code false}{@code false}{@code false}{@code 0}
 
archive.tzp4Valid RAES encrypted ZIP file with valid key (e.g. password){@code true}{@code true}{@code false}{@code true}{@code 0}
archive.tzpValid RAES encrypted ZIP file with unknown key5{@code true}{@code false}{@code false}{@code true}{@code ?}
archive.tzpFalse positive: Plain directory{@code true}{@code true}{@code false}{@code true}{@code ?}
archive.tzpFalse positive: Plain file{@code true}{@code false}{@code true}{@code true}{@code ?}
archive.tzpFalse positive: Regular special file{@code true}{@code false}{@code false}{@code true}{@code ?}
archive.tzpTFile or directory does not exist{@code true}{@code false}{@code false}{@code false}{@code 0}
 
otherPlain directory{@code false}{@code true}{@code false}{@code true}{@code ?}
otherPlain file{@code false}{@code false}{@code true}{@code true}{@code ?}
otherRegular special file{@code false}{@code false}{@code false}{@code true}{@code ?}
otherTFile or directory does not exist{@code false}{@code false}{@code false}{@code false}{@code 0}
*
    *
  1. {@link #isArchive} doesn't check the true state of the file - it just * looks at its path: If the path ends with a configured archive file * suffix, {@code isArchive()} always returns {@code true}. *
  2. {@link #length} always returns {@code 0} if the path denotes a * valid archive file. * Otherwise, the return value of {@code length()} depends on the * platform and file system, which is indicated by {@code ?}. * For regular directories on Windows/NTFS for example, the return value * would be {@code 0}. *
  3. This example assumes that the TrueZIP Driver ZIP module is * present on the run-time class path.
  4. *
  5. This example assumes that the TrueZIP Driver TZP (ZIP.RAES) module is * present on the run-time class path.
  6. *
  7. The methods behave exactly the same for both archive.zip and * archive.tzp with one exception: If the key for a RAES encrypted * ZIP file remains unknown (e.g. because the user cancelled password * prompting), then these methods behave as if the true state of the path * were a special file, i.e. both {@link #isDirectory} and {@link #isFile} * return {@code false}, while {@link #exists} returns {@code true}.
  8. *
* * @author Christian Schlichtherle * @version $Id$ */ @DefaultAnnotation(NonNull.class) @Immutable @edu.umd.cs.findbugs.annotations.SuppressWarnings("JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS") public final class TFile extends File { private static final long serialVersionUID = 3617072259051821745L; /** The prefix of a UNC (a Windows concept). */ private static final String UNC_PREFIX = separator + separator; /** The file system roots. */ private static final Set ROOTS = Collections.unmodifiableSet( new TreeSet(Arrays.asList(listRoots()))); private static boolean lenient = true; /** The file system manager to use within this package. */ static final FsManager manager = FsManagerLocator.SINGLETON.get(); private static TArchiveDetector defaultDetector = TArchiveDetector.ALL; /** * The delegate is used to implement the behaviour of the file system * operations in case this instance represents neither an archive file * nor an entry in an archive file. * If this instance is constructed from another {@code File} * instance, then this field is initialized with that instance. *

* This enables federation of file system implementations and is essential * to enable the broken implementation in * {@link javax.swing.JFileChooser} to browse archive files. */ private transient File delegate; private transient TArchiveDetector detector; private transient @Nullable TFile innerArchive; private transient @Nullable TFile enclArchive; private transient @Nullable FsEntryName enclEntryName; /** * This refers to the file system controller if and only if this file * refers to a prospective archive file, otherwise it's {@code null}. * This field should be considered to be {@code final}! * * @see #readObject */ private transient volatile @Nullable FsController controller; /** * Copy constructor. * Equivalent to {@link #TFile(File, TArchiveDetector) * new TFile(template, getDefaultArchiveDetector())}. * * @param file a file object. */ public TFile(File file) { this(file, defaultDetector); } /** * Constructs a new {@code TFile} instance which may use the given * {@link TArchiveDetector} to detect any archive files in its path. * * @param file The file to decorate. If this is an instance * of this class, its fields are copied and the * {@code detector} parameter is not used to scan the path. * @param detector The object to use for the detection of any archive files * in the path if necessary. * This parameter is ignored if and only if {@code file} is an * instance of this class. * Otherwise, if it's {@code null}, the * {@link #getDefaultArchiveDetector() default archive detector} is * used. */ public TFile( final File file, final @CheckForNull TArchiveDetector detector) { super(file.getPath()); if (file instanceof TFile) { final TFile tfile = (TFile) file; this.delegate = tfile.delegate; this.detector = tfile.detector; this.enclArchive = tfile.enclArchive; this.enclEntryName = tfile.enclEntryName; this.innerArchive = tfile.isArchive() ? this : tfile.innerArchive; this.controller = tfile.controller; } else { this.delegate = file; this.detector = null != detector ? detector : defaultDetector; scan(null); } assert invariants(); } /** * Equivalent to {@link #TFile(String, TArchiveDetector) * new TFile(path, getDefaultArchiveDetector())}. * * @param path a file path. */ public TFile(String path) { this(path, defaultDetector); } /** * Constructs a new {@code TFile} instance which uses the given * {@link TArchiveDetector} to detect any archive files in its path. * * @param path The path of the file. * @param detector The object to use for the detection of any archive files * in the path. * If this is {@code null}, the * {@link #getDefaultArchiveDetector() default archive detector} is * used. */ public TFile( final String path, final @CheckForNull TArchiveDetector detector) { super(path); this.delegate = new File(path); this.detector = null != detector ? detector : defaultDetector; scan(null); assert invariants(); } /** * Equivalent to {@link #TFile(String, String, TArchiveDetector) * new TFile(parent, child, getDefaultArchiveDetector())}. * * @param parent The path of the parent directory. * The {@link #getDefaultArchiveDetector() default archive detector} * is used to detect any archive files in the path of this * {@code TFile} instance. * @param member The child path as a {@link String}. */ public TFile(String parent, String member) { this(parent, member, defaultDetector); } /** * Constructs a new {@code TFile} instance which uses the given * {@link TArchiveDetector} to detect any archive files in its path. * * @param parent The parent path as a {@link String}. * @param member The child path as a {@link String}. * @param detector The object to use for the detection of any archive files * in the path. * If this is {@code null}, the * {@link #getDefaultArchiveDetector() default archive detector} is * used. */ public TFile( final String parent, final String member, final @CheckForNull TArchiveDetector detector) { super(parent, member); this.delegate = new File(parent, member); this.detector = null != detector ? detector : defaultDetector; scan(null); assert invariants(); } /** * Equivalent to {@link #TFile(File, String, TArchiveDetector) * new TFile(parent, child, null)}. * * @param parent The parent directory as a {@code TFile} instance. * If this parameter is an instance of this class, its * {@code TArchiveDetector} is used to detect any archive files * in the path of this {@code TFile} instance. * Otherwise, the * {@link #getDefaultArchiveDetector() default archive detector} * is used. * @param member The child path as a {@link String}. */ public TFile(File parent, String member) { this(parent, member, null); } /** * Constructs a new {@code TFile} instance which uses the given * {@link TArchiveDetector} to detect any archive files in its path * and configure their parameters. * * @param parent The parent directory as a {@code TFile} instance. * @param member The child path as a {@link String}. * @param detector The object to use for the detection of any archive files * in the path. * If this is {@code null} and {@code parent} is an * instance of this class, the archive detector is copied from * {@code parent}. * If this is {@code null} and {@code parent} is * not an instance of this class, the * {@link #getDefaultArchiveDetector() default archive detector} * is used. */ public TFile( final File parent, final String member, final @CheckForNull TArchiveDetector detector) { super(parent, member); this.delegate = new File(parent, member); if (parent instanceof TFile) { final TFile tparent = (TFile) parent; this.detector = null != detector ? detector : tparent.detector; scan(tparent); } else { this.detector = null != detector ? detector : defaultDetector; scan(null); } assert invariants(); } /** * Constructs a new {@code TFile} instance from the given {@code uri}. * This method behaves similar to the super class constructor * {@link File#File(URI)} with the following amendment: * If the URI matches the pattern {@code scheme:file:path!/entry}, * then the constructed file object treats the URI like an entry in the * federated file system (i.e. prospective archive file) of the type named * {@code scheme}. * This may be recursively applied to access archive entries within other * archive files. *

* Note that the constructed {@code TFile} instance uses the * {@link #getDefaultArchiveDetector() default archive detector} to look * up archive drivers for the named scheme(s). * * @param uri an absolute URI with a scheme supported by the * {@link #getDefaultArchiveDetector() default archive detector}. * @throws IllegalArgumentException if the given URI does not conform to * the syntax constraints for {@link FsPath}s or * {@link File#File(URI)}. */ public TFile(URI uri) { this(FsPath.create(uri, CANONICALIZE), defaultDetector); } /** * Constructs a new {@code TFile} instance from the given {@code path}. * This method behaves similar to the super class constructor * {@link File#File(URI)} with the following amendment: * If the path matches the pattern {@code scheme:file:path!/entry}, * then the constructed file object treats the URI like an entry in the * federated file system (i.e. prospective archive file) of the type named * {@code scheme}. * This may be recursively applied to access archive entries within other * archive files. *

* Note that the constructed {@code TFile} instance uses the * {@link #getDefaultArchiveDetector() default archive detector} to look * up archive drivers for the named scheme(s). * * @param path an absolute path with a scheme supported by the * {@link #getDefaultArchiveDetector() default archive detector}. * @throws IllegalArgumentException if the given URI does not conform to * the syntax constraints for {@link FsPath}s or * {@link File#File(URI)}. */ public TFile(FsPath path) { this(path, defaultDetector); } private TFile(FsPath path, TArchiveDetector detector) { super(path.toHierarchicalUri()); parse(path, detector); } private void parse( final FsPath path, final TArchiveDetector detector) { this.delegate = new File(super.getPath()); this.detector = detector; final FsMountPoint mp = path.getMountPoint(); final FsPath mpp = mp.getPath(); final FsEntryName en; if (null == mpp) { assert !path.toUri().isOpaque(); this.enclArchive = null; this.enclEntryName = null; this.innerArchive = null; } else if ((en = path.getEntryName()).isRoot()) { assert path.toUri().isOpaque(); if (mpp.toUri().isOpaque()) { this.enclArchive = new TFile(mpp.getMountPoint(), detector); this.enclEntryName = mpp.getEntryName(); } else { this.enclArchive = null; this.enclEntryName = null; } this.innerArchive = this; } else { assert path.toUri().isOpaque(); this.enclArchive = new TFile(mp, detector); this.enclEntryName = en; this.innerArchive = this.enclArchive; } assert invariants(); } @SuppressWarnings("LeakingThisInConstructor") private TFile( final FsMountPoint mountPoint, final TArchiveDetector detector) { super(mountPoint.toHierarchicalUri()); this.delegate = new File(super.getPath()); this.detector = detector; final FsPath mountPointPath = mountPoint.getPath(); if (null == mountPointPath) { assert !mountPoint.toUri().isOpaque(); this.enclArchive = null; this.enclEntryName = null; this.innerArchive = null; } else { assert mountPoint.toUri().isOpaque(); if (mountPointPath.toUri().isOpaque()) { this.enclArchive = new TFile(mountPointPath.getMountPoint(), detector); this.enclEntryName = mountPointPath.getEntryName(); } else { this.enclArchive = null; this.enclEntryName = null; } this.innerArchive = this; } assert invariants(); } @SuppressWarnings("LeakingThisInConstructor") private TFile( final File delegate, final @CheckForNull TFile innerArchive, final TArchiveDetector detector) { super(delegate.getPath()); this.delegate = delegate; final String path = delegate.getPath(); if (null != innerArchive) { final int innerArchivePathLength = innerArchive.getPath().length(); if (path.length() == innerArchivePathLength) { this.detector = innerArchive.detector; this.innerArchive = this; this.enclArchive = innerArchive.enclArchive; this.enclEntryName = innerArchive.enclEntryName; } else { this.detector = detector; this.innerArchive = this.enclArchive = innerArchive; try { this.enclEntryName = new FsEntryName( new UriBuilder() .path( path.substring(innerArchivePathLength + 1) // cut off leading separatorChar .replace(separatorChar, SEPARATOR_CHAR)) .getUri(), CANONICALIZE); } catch (URISyntaxException ex) { throw new AssertionError(ex); } } } else { this.detector = detector; } assert invariants(); } /** * Initialize this file object by scanning its path for archive * files, using the given {@code ancestor} file (i.e. a direct or * indirect parent file) if any. * {@code delegate} and {@code detector} must already be initialized! * Must not be called to re-initialize this object! */ private void scan(final @CheckForNull TFile ancestor) { final String path = super.getPath(); assert ancestor == null || path.startsWith(ancestor.getPath()); assert delegate.getPath().equals(path); assert null != detector; final StringBuilder enclEntryNameBuf = new StringBuilder(path.length()); scan(ancestor, detector, 0, path, enclEntryNameBuf, new Splitter(separatorChar, false)); try { enclEntryName = 0 >= enclEntryNameBuf.length() ? null : new FsEntryName( new UriBuilder().path(enclEntryNameBuf.toString()).getUri(), CANONICALIZE); } catch (URISyntaxException ex) { throw new AssertionError(ex); } } private void scan( @CheckForNull TFile ancestor, TArchiveDetector detector, int skip, final String path, final StringBuilder enclEntryNameBuf, final Splitter splitter) { if (path == null) { assert enclArchive == null; enclEntryNameBuf.setLength(0); return; } splitter.split(path); final String parent = splitter.getParentPath(); final String member = splitter.getMemberName(); if (member.length() == 0 || ".".equals(member)) { // Fall through. } else if ("..".equals(member)) { skip++; } else if (skip > 0) { skip--; } else { if (ancestor != null) { final int pathLen = path.length(); final int ancestorPathLen = ancestor.getPath().length(); if (pathLen == ancestorPathLen) { // Found ancestor: Process it and stop. // Using the following assertion would be wrong: // enclEntryNameBuf may indeed be null if the full path // ends with just a single dot after the last separator, // i.e. the base name is ".", indicating the current // directory. // assert enclEntryNameBuf.getLength() > 0; enclArchive = ancestor.innerArchive; if (!ancestor.isArchive()) { if (ancestor.isEntry()) { if (enclEntryNameBuf.length() > 0) { enclEntryNameBuf.insert(0, '/'); enclEntryNameBuf.insert(0, ancestor.enclEntryName.getPath()); } else { // TODO: Simplify this! // Example: new TFile(new TFile(new TFile("archive.zip"), "entry"), ".") assert enclArchive == ancestor.enclArchive; enclEntryNameBuf.append(ancestor.enclEntryName.getPath()); } } else { assert enclArchive == null; enclEntryNameBuf.setLength(0); } } else if (enclEntryNameBuf.length() <= 0) { // TODO: Simplify this! // Example: new TFile(new TFile("archive.zip"), ".") assert enclArchive == ancestor; innerArchive = this; enclArchive = ancestor.enclArchive; if (ancestor.enclEntryName != null) enclEntryNameBuf.append(ancestor.enclEntryName.getPath()); } if (innerArchive != this) innerArchive = enclArchive; return; } else if (pathLen < ancestorPathLen) { detector = ancestor.detector; ancestor = ancestor.enclArchive; } } final boolean isArchive = detector.getScheme(path) != null; if (enclEntryNameBuf.length() > 0) { if (isArchive) { enclArchive = new TFile(path, detector); // use the same detector for the parent directory if (innerArchive != this) innerArchive = enclArchive; return; } enclEntryNameBuf.insert(0, '/'); enclEntryNameBuf.insert(0, member); } else { if (isArchive) innerArchive = this; enclEntryNameBuf.append(member); } } scan(ancestor, detector, skip, parent, enclEntryNameBuf, splitter); } private Object writeReplace() throws ObjectStreamException { return getAbsoluteFile(); } private void writeObject(ObjectOutputStream out) throws IOException { out.writeObject(toURI()); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { parse( FsPath.create((URI) in.readObject(), CANONICALIZE), defaultDetector); } /** * Checks the invariants of this class and throws an AssertionError if * any is violated even if assertion checking is disabled. *

* The constructors call this method like this: *

{@code assert invariants();}
* This calls the method if and only if assertions are enabled in order * to assert that the instance invariants are properly obeyed. * If assertions are disabled, the call to this method is thrown away by * the HotSpot compiler, so there is no performance penalty. * * @throws AssertionError If any invariant is violated even if assertions * are disabled. * @return {@code true} */ private boolean invariants() { assert null != delegate; assert !(delegate instanceof TFile); assert delegate.getPath().equals(super.getPath()); assert null != detector; assert (null != innerArchive) == (getInnerEntryName() != null); assert (null != enclArchive) == (enclEntryName != null); assert this != enclArchive; assert (this == innerArchive) ^ (innerArchive == enclArchive && null == controller); assert null == enclArchive || Paths.contains( enclArchive.getPath(), delegate.getParentFile().getPath(), separatorChar) && !enclEntryName.toString().isEmpty(); return true; } /** * Commits all unsynchronized changes to the contents of all federated file * systems (i.e. prospective archive files) to their respective parent file * system, releases the associated resources (i.e. target archive files) * for access by third parties (e.g. other processes), cleans up any * temporary allocated resources (e.g. temporary files) and purges any * cached data. * Note that temporary files may get used even if the archive files where * accessed read-only. * * @param options a bit field of synchronization options. * @throws IllegalArgumentException if the combination of synchronization * options is illegal, e.g. if * {@code FsSyncOption.FORCE_CLOSE_INPUT} is cleared and * {@code FsSyncOption.FORCE_CLOSE_OUTPUT} is set or if the * synchronization option {@code FsSyncOption.ABORT_CHANGES} is set. * @throws FsSyncWarningException if only warning conditions * occur. * This implies that the respective parent file system has been * updated with constraints, such as a failure to set the last * modification time of the entry for the federated file system * (i.e. archive file) in its parent file system. * @throws FsSyncException if any error conditions occur. * This implies loss of data! */ public static void sync(BitField options) throws FsSyncException { manager.sync(options); } /** * Commits all unsynchronized changes to the contents of all federated file * systems (i.e. prospective archive files) identified by {@code archive} * and all its member federated file systems to their respective parent * file system, releases the associated resources (i.e. target archive * files) for access by third parties (e.g. other processes), cleans up any * temporary allocated resources (e.g. temporary files) and purges any * cached data. * Note that temporary files may get used even if the archive files where * accessed read-only. *

* If a client application needs to sync an individual archive file, * the following idiom could be used: *

{@code
     * if (file.isArchive() && file.getEnclArchive() == null) // filter top level federated file system
     *   if (file.isDirectory()) // ignore false positives
     *     TFile.sync(file); // sync federated file system and all its members
     * }
* Again, this will also sync all federated file systems which are * located within the file system referred to by {@code file}. * * @param archive a top level federated file system, i.e. archive file. * @param options a bit field of synchronization options. * @throws IllegalArgumentException if {@code archive} is not a top level * federated file system or the combination of synchronization * options is illegal, e.g. if * {@code FsSyncOption.FORCE_CLOSE_INPUT} is cleared and * {@code FsSyncOption.FORCE_CLOSE_OUTPUT} is set or if the * synchronization option {@code FsSyncOption.ABORT_CHANGES} is set. * @throws FsSyncWarningException if only warning conditions * occur. * This implies that the respective parent file system has been * updated with constraints, such as a failure to set the last * modification time of the entry for the federated file system * (i.e. archive file) in its parent file system. * @throws FsSyncException if any error conditions occur. * This implies loss of data! * @see #sync(BitField) */ public static void sync( final TFile archive, final BitField options) throws FsSyncException { if (!archive.isArchive()) throw new IllegalArgumentException(archive + " (not an archive file)"); if (null != archive.getEnclArchive()) throw new IllegalArgumentException(archive + " (not a top level archive file)"); new FsFilteringManager( manager, archive .getController() .getModel() .getMountPoint()) .sync(options); } /** * Commits all unsynchronized changes to the contents of all federated file * systems (i.e. prospective archive files) to their respective parent file * system, releases the associated resources (i.e. target archive files) * for access by third parties (e.g. other processes), cleans up any * temporary allocated resources (e.g. temporary files) and purges any * cached data. * Note that temporary files may get used even if the archive files where * accessed read-only. *

* This method is equivalent to calling * {@link #sync(BitField) sync(FsManager.UMOUNT)}. * * @throws FsSyncWarningException if only warning conditions * occur. * This implies that the respective parent file system has been * updated with constraints, such as a failure to set the last * modification time of the entry for the federated file system * (i.e. archive file) in its parent file system. * @throws FsSyncException if any error conditions occur. * This implies loss of data! * @see #sync(BitField) */ public static void umount() throws FsSyncException { sync(UMOUNT); } /** * Commits all unsynchronized changes to the contents of all federated file * systems (i.e. prospective archive files) to their respective parent file * system, releases the associated resources (i.e. target archive files) * for access by third parties (e.g. other processes), cleans up any * temporary allocated resources (e.g. temporary files) and purges any * cached data. * Note that temporary files may get used even if the archive files where * accessed read-only. *

* This method is equivalent to calling * {@link #sync(BitField) sync( BitField.of(FsSyncOption.CLEAR_CACHE) .set(FsSyncOption.FORCE_CLOSE_INPUT, closeStreams) .set(FsSyncOption.FORCE_CLOSE_OUTPUT, closeStreams)) * }. * * @param closeStreams see {@link FsSyncOption#FORCE_CLOSE_INPUT} and * {@link FsSyncOption#FORCE_CLOSE_OUTPUT}. * @throws FsSyncWarningException if only warning conditions * occur. * This implies that the respective parent file system has been * updated with constraints, such as a failure to set the last * modification time of the entry for the federated file system * (i.e. prospective archive file) in its parent file system. * @throws FsSyncException if any error conditions occur. * This implies loss of data! * @see #sync(BitField) */ public static void umount(boolean closeStreams) throws FsSyncException { sync( BitField.of(CLEAR_CACHE) .set(FORCE_CLOSE_INPUT, closeStreams) .set(FORCE_CLOSE_OUTPUT, closeStreams)); } /** * Commits all unsynchronized changes to the contents of all federated file * systems (i.e. prospective archive files) to their respective parent file * system, releases the associated resources (i.e. target archive files) * for access by third parties (e.g. other processes), cleans up any * temporary allocated resources (e.g. temporary files) and purges any * cached data. * Note that temporary files may get used even if the archive files where * accessed read-only. *

* This method is equivalent to calling * {@link #sync(BitField) sync( BitField.of(FsSyncOption.CLEAR_CACHE) .set(FsSyncOption.WAIT_CLOSE_INPUT, waitForInputStreams) .set(FsSyncOption.FORCE_CLOSE_INPUT, closeInputStreams) .set(FsSyncOption.WAIT_CLOSE_OUTPUT, waitForOutputStreams) .set(FsSyncOption.FORCE_CLOSE_OUTPUT, closeOutputStreams)) * }. * * @param waitForInputStreams see {@link FsSyncOption#WAIT_CLOSE_INPUT}. * @param closeInputStreams see {@link FsSyncOption#FORCE_CLOSE_INPUT}. * @param waitForOutputStreams see {@link FsSyncOption#WAIT_CLOSE_OUTPUT}. * @param closeOutputStreams see {@link FsSyncOption#FORCE_CLOSE_OUTPUT}. * @throws FsSyncWarningException if only warning conditions * occur. * This implies that the respective parent file system has been * updated with constraints, such as a failure to set the last * modification time of the entry for the federated file system * (i.e. prospective archive file) in its parent file system. * @throws FsSyncException if any error conditions occur. * This implies loss of data! * @see #sync(BitField) */ public static void umount( boolean waitForInputStreams, boolean closeInputStreams, boolean waitForOutputStreams, boolean closeOutputStreams) throws FsSyncException { sync( BitField.of(CLEAR_CACHE) .set(WAIT_CLOSE_INPUT, waitForInputStreams) .set(FORCE_CLOSE_INPUT, closeInputStreams) .set(WAIT_CLOSE_OUTPUT, waitForOutputStreams) .set(FORCE_CLOSE_OUTPUT, closeOutputStreams)); } /** * Commits all unsynchronized changes to the contents of all federated file * systems (i.e. prospective archive files) to their respective parent file * system, releases the associated resources (i.e. target archive files) * for access by third parties (e.g. other processes), cleans up any * temporary allocated resources (e.g. temporary files) and purges any * cached data. * Note that temporary files may get used even if the archive files where * accessed read-only. *

* This method is equivalent to calling * {@link #sync(BitField) sync(archive, FsManager.UMOUNT) * }. * * @param archive a top level federated file system, i.e. prospective * archive file. * @throws FsSyncWarningException if only warning conditions * occur. * This implies that the respective parent file system has been * updated with constraints, such as a failure to set the last * modification time of the entry for the federated file system * (i.e. prospective archive file) in its parent file system. * @throws FsSyncException if any error conditions occur. * This implies loss of data! * @see #sync(TFile, BitField) */ public static void umount(TFile archive) throws FsSyncException { sync(archive, UMOUNT); } /** * Commits all unsynchronized changes to the contents of all federated file * systems (i.e. prospective archive files) to their respective parent file * system, releases the associated resources (i.e. target archive files) * for access by third parties (e.g. other processes), cleans up any * temporary allocated resources (e.g. temporary files) and purges any * cached data. * Note that temporary files may get used even if the archive files where * accessed read-only. *

* This method is equivalent to calling * {@link #sync(BitField) sync( archive, BitField.of(FsSyncOption.CLEAR_CACHE) .set(FsSyncOption.FORCE_CLOSE_INPUT, closeStreams) .set(FsSyncOption.FORCE_CLOSE_OUTPUT, closeStreams)) * }. * * @param archive a top level federated file system, i.e. prospective * archive file. * @param closeStreams see {@link FsSyncOption#FORCE_CLOSE_INPUT} and * {@link FsSyncOption#FORCE_CLOSE_OUTPUT}. * @throws IllegalArgumentException if {@code archive} is not a top level * federated file system or the combination of synchronization * options is illegal, e.g. if * {@code FsSyncOption.FORCE_CLOSE_INPUT} is cleared and * {@code FsSyncOption.FORCE_CLOSE_OUTPUT} is set or if the * synchronization option {@code FsSyncOption.ABORT_CHANGES} is set. * @throws FsSyncWarningException if only warning conditions * occur. * This implies that the respective parent file system has been * updated with constraints, such as a failure to set the last * modification time of the entry for the federated file system * (i.e. prospective archive file) in its parent file system. * @throws FsSyncException if any error conditions occur. * This implies loss of data! * @see #sync(TFile, BitField) */ public static void umount(TFile archive, boolean closeStreams) throws FsSyncException { sync( archive, BitField.of(CLEAR_CACHE) .set(FORCE_CLOSE_INPUT, closeStreams) .set(FORCE_CLOSE_OUTPUT, closeStreams)); } /** * Commits all unsynchronized changes to the contents of all federated file * systems (i.e. prospective archive files) to their respective parent file * system, releases the associated resources (i.e. target archive files) * for access by third parties (e.g. other processes), cleans up any * temporary allocated resources (e.g. temporary files) and purges any * cached data. * Note that temporary files may get used even if the archive files where * accessed read-only. *

* This method is equivalent to calling * {@link #sync(BitField) sync( archive, BitField.of(FsSyncOption.CLEAR_CACHE) .set(FsSyncOption.WAIT_CLOSE_INPUT, waitForInputStreams) .set(FsSyncOption.FORCE_CLOSE_INPUT, closeInputStreams) .set(FsSyncOption.WAIT_CLOSE_OUTPUT, waitForOutputStreams) .set(FsSyncOption.FORCE_CLOSE_OUTPUT, closeOutputStreams)) * }. * * @param archive a top level federated file system, i.e. prospective * archive file. * @param waitForInputStreams see {@link FsSyncOption#WAIT_CLOSE_INPUT}. * @param closeInputStreams see {@link FsSyncOption#FORCE_CLOSE_INPUT}. * @param waitForOutputStreams see {@link FsSyncOption#WAIT_CLOSE_OUTPUT}. * @param closeOutputStreams see {@link FsSyncOption#FORCE_CLOSE_OUTPUT}. * @throws IllegalArgumentException if {@code archive} is not a top level * federated file system or the combination of synchronization * options is illegal, e.g. if * {@code FsSyncOption.FORCE_CLOSE_INPUT} is cleared and * {@code FsSyncOption.FORCE_CLOSE_OUTPUT} is set or if the * synchronization option {@code FsSyncOption.ABORT_CHANGES} is set. * @throws FsSyncWarningException if only warning conditions * occur. * This implies that the respective parent file system has been * updated with constraints, such as a failure to set the last * modification time of the entry for the federated file system * (i.e. prospective archive file) in its parent file system. * @throws FsSyncException if any error conditions occur. * This implies loss of data! * @see #sync(TFile, BitField) */ public static void umount(TFile archive, boolean waitForInputStreams, boolean closeInputStreams, boolean waitForOutputStreams, boolean closeOutputStreams) throws FsSyncException { sync( archive, BitField.of(CLEAR_CACHE) .set(WAIT_CLOSE_INPUT, waitForInputStreams) .set(FORCE_CLOSE_INPUT, closeInputStreams) .set(WAIT_CLOSE_OUTPUT, waitForOutputStreams) .set(FORCE_CLOSE_OUTPUT, closeOutputStreams)); } /** * Returns the value of the class property {@code lenient}. * By default, the value of this class property is {@code true}. * * @see #setLenient(boolean) * @return The value of the class property {@code lenient}. */ public static boolean isLenient() { return lenient; } /** * Sets the value of the class property {@code lenient}. * This class property controls whether archive files and their member * directories get automatically created whenever required. * By default, the value of this class property is {@code true}! *

* Consider the following path: {@code a/outer.zip/b/inner.zip/c}. * Now let's assume that {@code a} exists as a plain directory in the OS * file system, while all other parts of this path don't, and that the * module TrueZIP Driver ZIP is present on the run-time class path in order * to recognize {@code outer.zip} and {@code inner.zip} as ZIP files by * default. *

* Now, if this class property is set to {@code false}, then an application * would have to call {@code new TFile("a/outer.zip/b/inner.zip").mkdirs()} * before it could actually create the innermost {@code c} entry as a file * or directory. *

* More formally, before an application could access an entry in a * federated file system, all its parent directories would need to exist, * including archive files. * This emulates the behaviour of any OS file system type. *

* If this class property is set to {@code true} however, then any missing * parent directories (including archive files) up to the outermost archive * file {@code outer.zip} would get automatically created when using * operations to create the innermost element of the path {@code c}. *

* This allows applications to succeed with doing this: * {@code new TFile("a/outer.zip/b/inner.zip/c").createNewFile()}, * or that: * {@code new TFileOutputStream("a/outer.zip/b/inner.zip/c")}. *

* Note that in either case the parent directory of the outermost archive * file {@code a} must exist - TrueZIP does not automatically create * directories in the OS file system! * * @param lenient the value of the class property {@code lenient}. * @see #isLenient() */ public static void setLenient(boolean lenient) { TFile.lenient = lenient; } /** * Returns the {@link TArchiveDetector} to use if no archive detector is * explicitly passed to the constructor of a {@code TFile} instance. *

* This class property is initially set to * {@link TArchiveDetector#ALL} * * @return The {@link TArchiveDetector} to use if no archive detector is * explicitly passed to the constructor of a {@code TFile} instance. * @see #setDefaultArchiveDetector */ public static TArchiveDetector getDefaultArchiveDetector() { return defaultDetector; } /** * Sets the {@link TArchiveDetector} to use if no archive detector is * explicitly passed to the constructor of a {@code TFile} instance. * When a new {@code TFile} instance is constructed, but no archive * detector is provided, then the value of this class property is used. * So changing the value of this class property affects only subsequently * constructed {@code TFile} instances - not any existing ones. * * @param detector the {@link TArchiveDetector} to use for subsequently * constructed {@code TFile} instances if no archive detector is * explicitly provided to the constructor * @see #getDefaultArchiveDetector() */ public static void setDefaultArchiveDetector(TArchiveDetector detector) { if (null == detector) throw new NullPointerException(); TFile.defaultDetector = detector; } /** * Returns the first parent directory (starting from this file) which is * not an archive file or a file located in an archive file. * * @return The first parent directory (starting from this file) which is * not an archive file or a file located in an archive * file. */ public @Nullable TFile getNonArchivedParentFile() { final TFile enclArchive = this.enclArchive; return null != enclArchive ? enclArchive.getNonArchivedParentFile() : getParentFile(); } /** {@inheritDoc} */ @Override public @Nullable String getParent() { return delegate.getParent(); } /** {@inheritDoc} */ @Override public @Nullable TFile getParentFile() { final File parent = delegate.getParentFile(); if (parent == null) return null; if (null != enclArchive && enclArchive.getPath().length() == parent.getPath().length()) { assert enclArchive.getPath().equals(parent.getPath()); return enclArchive; } // This is not only called for performance reasons, but also in order // to prevent the parent path from being rescanned for archive files // with a different detector, which could trigger an update and // reconfiguration of the respective file system controller! return new TFile(parent, enclArchive, detector); } /** {@inheritDoc} */ @Override public TFile getAbsoluteFile() { String p = getAbsolutePath(); return p.equals(getPath()) ? this : new TFile(p, detector); } /** {@inheritDoc} */ @Override public String getAbsolutePath() { return delegate.getAbsolutePath(); } /** * Similar to {@link #getAbsoluteFile()}, but removes any * {@code "."} and {@code ".."} directories from the path name wherever * possible. * The result is similar to {@link #getCanonicalFile()}, but symbolic * links are not resolved. * This may be useful if {@code getCanonicalFile()} throws an * IOException. * * @return The normalized absolute file object denoting the same file or * directory as this instance. * @see #getCanonicalFile() * @see #getNormalizedFile() */ public TFile getNormalizedAbsoluteFile() { String p = getNormalizedAbsolutePath(); return p.equals(getPath()) ? this : new TFile(p, detector); } /** * Similar to {@link #getAbsolutePath()}, but removes any redundant * {@code "."} and {@code ".."} directories from the path name. * The result is similar to {@link #getCanonicalPath()}, but symbolic * links are not resolved. * This may be useful if {@code getCanonicalPath()} throws an * IOException. * * @return The normalized absolute path string denoting the same file or * directory as this instance. * @see #getCanonicalPath() * @see #getNormalizedPath() */ public String getNormalizedAbsolutePath() { return Paths.normalize(getAbsolutePath(), separatorChar); } /** * Removes any {@code "."} and {@code ".."} directories from the path name * wherever possible. * * @return The normalized file object denoting the same file or * directory as this instance. */ public TFile getNormalizedFile() { String p = getNormalizedPath(); return p.equals(getPath()) ? this : new TFile(p, detector); } /** * Removes any redundant {@code "."}, {@code ".."} directories from the * path name. * * @return The normalized path string denoting the same file or * directory as this instance. */ public String getNormalizedPath() { return Paths.normalize(getPath(), separatorChar); } /** {@inheritDoc} */ @Override public TFile getCanonicalFile() throws IOException { String p = getCanonicalPath(); return p.equals(getPath()) ? this : new TFile(p, detector); } /** {@inheritDoc} */ @Override public String getCanonicalPath() throws IOException { return delegate.getCanonicalPath(); } /** * This convenience method simply returns the canonical form of this * abstract path or the normalized absolute form if resolving the * prior fails. * * @return The canonical or absolute path of this file as an * instance of this class. */ public TFile getCanOrAbsFile() { String p = getCanOrAbsPath(); return p.equals(getPath()) ? this : new TFile(p, detector); } /** * This convenience method simply returns the canonical form of this * abstract path or the normalized absolute form if resolving the * prior fails. * * @return The canonical or absolute path of this file as a * {@code String} instance. */ public String getCanOrAbsPath() { try { return getCanonicalPath(); } catch (IOException ex) { return Paths.normalize(getAbsolutePath(), separatorChar); } } /** * Returns {@code true} if and only if the path represented by this * instance denotes an archive file. * Whether or not this is true solely depends on the * {@link TArchiveDetector} which was used to construct this file object * - no file system tests are performed by this method! * * @return {@code true} if and only if the path represented by this * instance denotes an archive file. * @see #isEntry * @see #isDirectory * @see Detecting Archive Paths and False Positives */ public boolean isArchive() { return this == innerArchive; } /** * Returns {@code true} if and only if this file object addresses an * entry located within an archive file. * Whether or not this is true solely depends on the * {@link TArchiveDetector} which was used to construct this file object * - no file system tests are performed by this method! * * @return {@code true} if and only if this file object addresses an * entry located within an archive file. * @see #isArchive * @see #isDirectory * @see Detecting Archive Paths and False Positives */ public boolean isEntry() { return enclEntryName != null; } /** * Returns the innermost archive file object for this file object. * That is, if this object addresses an archive file, then this method * returns {@code this}. * If this object addresses an entry located within an archive file, then * this methods returns the file object representing the enclosing archive * file, or {@code null} otherwise. *

* This method always returns a normalized path, i.e. all occurences of * {@code "."} and {@code ".."} in the path name are removed according to * their meaning wherever possible. *

* In order to support unlimited nesting levels, this method returns * a {@code TFile} instance which may recursively address an entry within * another archive file. * * @return The innermost archive path object for this path object. */ public @CheckForNull TFile getInnerArchive() { return innerArchive; } /** * Returns the entry name relative to the innermost archive file. * That is, if this object addresses an archive file, then this method * returns the empty string {@code ""}. * If this object addresses an entry located within an archive file, * then this method returns the relative path of the entry in the * enclosing archive file separated by the entry separator character * {@code '/'}, or {@code null} otherwise. *

* This method always returns an undotified path, i.e. all redundant * occurences of {@code "."} and {@code ".."} in the path are removed * wherever possible. * * @return The entry name relative to the innermost archive file. */ public @Nullable String getInnerEntryName() { return this == innerArchive ? ROOT.getPath() : null == enclEntryName ? null : enclEntryName.getPath(); } @Nullable FsEntryName getInnerFsEntryName() { return this == innerArchive ? ROOT : enclEntryName; } /** * Returns the enclosing archive file object for this file object. * That is, if this object addresses an entry located within an archive * file, then this method returns the file object representing the * enclosing archive file, or {@code null} otherwise. *

* This method always returns a normalized path, i.e. all occurences of * {@code "."} and {@code ".."} in the path name are removed according to * their meaning wherever possible. *

* In order to support unlimited nesting levels, this method returns * a {@code TFile} instance which may recursively address an entry within * another archive file. * * @return The enclosing archive file in this path. */ public @CheckForNull TFile getEnclArchive() { return enclArchive; } /** * Returns the entry name relative to the enclosing archive file. * That is, if this object addresses an entry located within an archive * file, then this method returns the relative path of the entry in the * enclosing archive file separated by the entry separator character * {@code '/'}, or {@code null} otherwise. *

* This method always returns an undotified path, i.e. all redundant * occurences of {@code "."} and {@code ".."} in the path are removed * wherever possible. * * @return The entry name relative to the enclosing archive file. */ public @Nullable String getEnclEntryName() { return null == enclEntryName ? null : enclEntryName.getPath(); } @Nullable FsEntryName getEnclFsEntryName() { return enclEntryName; } /** * Returns the {@link TArchiveDetector} that was used to detect any archive * files in the path of this file object at construction time. * * @return The {@link TArchiveDetector} that was used to detect any archive * files in the path of this file object at construction time. */ public TArchiveDetector getArchiveDetector() { return detector; } /** * Returns the decorated file object. *

* If this file instance has been created from a {@link FileSystemView}, * the decorated file object may be an instance of a sibling class, i.e. * another sub-class of {@link File}. * * @return An instance of the {@link File File} class or any of its * sub-classes, but never an instance of this class and never * {@code null}. */ public File getFile() { return delegate; } /** * Returns a file system controller if and only if the path denotes an * archive file, or {@code null} otherwise. *

* TODO: Consider making this public in order to enable applications to * get access to archive entry properties like this: *


     * 
* * @return A file system controller if and only if the path denotes an * archive file, or {@code null} otherwise. */ @Nullable FsController getController() { if (this != innerArchive || null != controller) return controller; assert this == innerArchive; final String path = Paths.normalize(delegate.getPath(), separatorChar); final FsScheme scheme = detector.getScheme(path); if (null == scheme) throw new ServiceConfigurationError( "unknown file system scheme for path \"" + path + "\"! Check run-time class path configuration."); final FsMountPoint mountPoint; try { mountPoint = new FsMountPoint(scheme, null == enclArchive ? new FsPath( delegate) : new FsPath( enclArchive .getController() .getModel() .getMountPoint(), enclEntryName)); } catch (URISyntaxException ex) { throw new AssertionError(ex); } return controller = manager.getController(mountPoint, detector); } /** * Returns {@code true} if and only if the path represented * by this instance is a direct or indirect parent of the path * represented by the given {@code file}. *

* Note: *

    *
  • This method uses the absolute path name of the given files. *
  • This method does not access the file system. * It just tests the paths. *
* * @param file The file object for the path to test for being a direct or * indirect child of the path of this instance. * @return {@code true} if and only if the path represented * by this instance is a direct or indirect parent of the path * represented by the given {@code file}. */ public boolean isParentOf(File file) { String a = this.getAbsolutePath(); String b = file.getAbsoluteFile().getParent(); return b != null ? Paths.contains(a, b, separatorChar) : false; } /** * Returns {@code true} if and only if the path represented * by this instance contains the path represented by the given {@code file}, * where a path is said to contain another path if and only * if it's equal or an ancestor of the other path. *

* Note: *

    *
  • This method uses the absolute path name of the given files. *
  • This method does not access the file system. * It just tests the paths. *
* * @param file The file object for the path to test for being contained by * the path of this instance. * @return {@code true} if and only if the path represented * by this instance contains the path represented by the given * {@code file} * @throws NullPointerException If the parameter is {@code null}. */ public boolean contains(File file) { return contains(this, file); } /** * Returns {@code true} if and only if the path represented * by {@code a} contains the path represented by {@code b}, * where a path is said to contain another path if and only * if it's equal or an ancestor of the other path. *

* Note: *

    *
  • This method uses the absolute path name of the given files. *
  • This method does not access the file system. * It just tests the paths. *
* * @param a the file to test for containing {@code b}. * @param b the file to test for being contained by {@code a}. * @return {@code true} if and only if the path represented * by {@code a} contains the path represented by {@code b}. * @throws NullPointerException If any parameter is {@code null}. */ public static boolean contains(File a, File b) { return Paths.contains( a.getAbsolutePath(), b.getAbsolutePath(), separatorChar); } /** * Returns {@code true} if and only if this file denotes a file system * root or a UNC (if running on the Windows platform). * * @return {@code true} if and only if this file denotes a file system * root or a UNC (if running on the Windows platform). */ public boolean isFileSystemRoot() { TFile canOrAbsFile = getCanOrAbsFile(); return ROOTS.contains(canOrAbsFile) || isUNC(canOrAbsFile.getPath()); } /** * Returns {@code true} if and only if this file denotes a UNC. * Note that this should be relevant on the Windows platform only. * * @return {@code true} if and only if this file denotes a UNC. */ public boolean isUNC() { return isUNC(getCanOrAbsPath()); } /** * Returns {@code true} if and only if this file denotes a UNC. * Note that this may be only relevant on the Windows platform. * * @param path a file path. * @return {@code true} if and only if {@code path} denotes a UNC. */ private static boolean isUNC(String path) { return path.startsWith(UNC_PREFIX) && path.indexOf(separatorChar, 2) > 2; } /** * {@inheritDoc} *

* The implementation in the class {@link TFile} delegates the call to its * {@link #getFile() decorated file}. * * @see #equals(Object) */ @Override public int hashCode() { return delegate.hashCode(); } /** * {@inheritDoc} *

* The implementation in the class {@link TFile} delegates the call to its * {@link #getFile() decorated file}. * This implies that only the hierarchicalized file system path * of this file instance is considered in the comparison. * E.g. {@code new TFile(FsPath.create("zip:file:/archive!/entry"))} and * {@code new TFile(FsPath.create("tar:file:/archive!/entry"))} would * compare equal because their hierarchicalized file system path is * {@code "file:/archive/entry"}. *

* More formally, let {@code a} and {@code b} be two TFile objects. * Then if the expression * {@code a.toFsPath().toHierarchicalUri().equals(b.toFsPath().toHierarchicalUri())} * is true, the expression {@code a.equals(b)} is also true. *

* Note that this does not work vice versa: * E.g. on Windows, the expression * {@code new TFile("file").equals(new TFile("FILE"))} is true, but * {@code new TFile("file").toFsPath().toHierarchicalUri().equals(new TFile("FILE").toFsPath().toHierarchicalUri())} * is false because {@link FsPath#equals(Object)} is case sensitive. * * @see #hashCode() * @see #compareTo(File) */ @Override @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"}) public boolean equals(Object that) { return delegate.equals(that); } /** * {@inheritDoc } *

* The implementation in the class {@link TFile} delegates the call to its * {@link #getFile() decorated file} object. * This implies that only the hierarchicalized file system path * of this file instance is considered in the comparison. * E.g. {@code new TFile(FsPath.create("zip:file:/archive!/entry"))} and * {@code new TFile(FsPath.create("tar:file:/archive!/entry"))} would * compare equal because their hierarchicalized file system path is * {@code "file:/archive/entry"}. *

* More formally, let {@code a} and {@code b} be two TFile objects. * Then if the expression * {@code a.toFsPath().toHierarchicalUri().compareTo(b.toFsPath().toHierarchicalUri()) == 0} * is true, the expression {@code a.compareTo(b) == 0} is also true. *

* Note that this does not work vice versa: * E.g. on Windows, the expression * {@code new TFile("file").compareTo(new TFile("FILE")) == 0} is true, but * {@code new TFile("file").toFsPath().toHierarchicalUri().compareTo(new TFile("FILE").toFsPath().toHierarchicalUri()) == 0} * is false because {@link FsPath#equals(Object)} is case sensitive. * * @see #equals(Object) */ @Override public int compareTo(File other) { return delegate.compareTo(other); } /** * Returns The top level archive file in the path or {@code null} * if this file object does not name an archive file. * A top level archive is not enclosed in another archive. * If this does not return {@code null}, this denotes the longest * part of the path which actually may (but does not need to) exist * as a plain file in the OS file system. */ public TFile getTopLevelArchive() { final TFile enclArchive = this.enclArchive; return null != enclArchive ? enclArchive.getTopLevelArchive() : innerArchive; } /** {@inheritDoc} */ @Override public String getPath() { return delegate.getPath(); } /** {@inheritDoc} */ @Override public String getName() { return delegate.getName(); } /** {@inheritDoc} */ @Override public boolean isAbsolute() { return delegate.isAbsolute(); } /** {@inheritDoc} */ @Override public boolean isHidden() { return delegate.isHidden(); } /** {@inheritDoc} */ @Override public String toString() { return delegate.toString(); } public FsPath toFsPath() { try { if (this == innerArchive) { final FsScheme scheme = detector.getScheme(delegate.getPath()); assert null != scheme; // make FindBugs happy! if (null != enclArchive) { return new FsPath( new FsMountPoint( scheme, new FsPath( new FsMountPoint(enclArchive.toURI(), CANONICALIZE), enclEntryName)), ROOT); } else { return new FsPath( new FsMountPoint(scheme, new FsPath(delegate)), ROOT); } } else if (null != enclArchive) { return new FsPath( new FsMountPoint(enclArchive.toURI(), CANONICALIZE), enclEntryName); } else { return new FsPath(delegate); } } catch (URISyntaxException ex) { throw new AssertionError(ex); } } /** {@inheritDoc} */ @Override public URI toURI() { try { if (this == innerArchive) { final FsScheme scheme = detector.getScheme(delegate.getPath()); assert null != scheme; // make FindBugs happy! if (null != enclArchive) { return new FsMountPoint( scheme, new FsPath( new FsMountPoint(enclArchive.toURI(), CANONICALIZE), enclEntryName)).toUri(); } else { return new FsMountPoint(scheme, new FsPath(delegate)).toUri(); } } else if (null != enclArchive) { return new FsPath( new FsMountPoint(enclArchive.toURI(), CANONICALIZE), enclEntryName).toUri(); } else { return delegate.toURI(); } } catch (URISyntaxException ex) { throw new AssertionError(ex); } } /** {@inheritDoc} */ @Deprecated @Override public URL toURL() throws MalformedURLException { return null != innerArchive ? toURI().toURL() : delegate.toURL(); } /** * This file system operation is virtually atomic. * * @see Detecting Archive Paths and False Positives */ @Override public boolean exists() { // This methods deviates from the general pattern of testing if // innerArchive is not null. // This optimization avoids mounting an archive file system just in // order to test for its existence. // See http://java.net/jira/browse/TRUEZIP-77 if (null != enclArchive) { try { return null != enclArchive.getController() .getEntry(getEnclFsEntryName()); } catch (IOException ex) { return false; } } return delegate.exists(); } /** * Similar to its super class implementation, but returns * {@code false} for a valid archive file, too. *

* For archive file validation its virtual file system gets mounted. * In case a RAES encrypted ZIP file gets mounted, the user gets prompted * for its password unless the default configuration for key management * hasn't been overridden. *

* This file system operation is virtually atomic. * * @see Detecting Archive Paths and False Positives */ @Override public boolean isFile() { if (null != innerArchive) { try { FsEntry entry = innerArchive.getController() .getEntry(getInnerFsEntryName()); return null != entry && entry.isType(FILE); } catch (IOException ex) { return false; } } return delegate.isFile(); } /** * Similar to its super class implementation, but returns * {@code true} for a valid archive file, too. *

* For archive file validation its virtual file system gets mounted. * In case a RAES encrypted ZIP file gets mounted, the user gets prompted * for its password unless the default configuration for key management * hasn't been overridden. *

* This file system operation is virtually atomic. * * @see Detecting Archive Paths and False Positives * @see #isArchive * @see #isEntry */ @Override public boolean isDirectory() { if (null != innerArchive) { try { FsEntry entry = innerArchive.getController() .getEntry(getInnerFsEntryName()); return null != entry && entry.isType(DIRECTORY); } catch (IOException ex) { return false; } } return delegate.isDirectory(); } /** * If this file is a true archive file, its archive driver is asked to * return an icon to be displayed for this file. * Otherwise, null is returned. * * @return An icon or {@code null}. */ public @CheckForNull Icon getOpenIcon() { if (this == innerArchive) { try { return getController().getOpenIcon(); } catch (IOException ex) { } } return null; } /** * If this file is a true archive file, its archive driver is asked to * return an icon to be displayed for this file. * Otherwise, null is returned. * * @return An icon or {@code null}. */ public @CheckForNull Icon getClosedIcon() { if (this == innerArchive) { try { return getController().getClosedIcon(); } catch (IOException ex) { } } return null; } /** {@inheritDoc} */ @Override public boolean canRead() { if (null != innerArchive) { try { return innerArchive.getController() .isReadable(getInnerFsEntryName()); } catch (IOException ex) { return false; } } return delegate.canRead(); } /** {@inheritDoc} */ @Override public boolean canWrite() { if (null != innerArchive) { try { return innerArchive.getController() .isWritable(getInnerFsEntryName()); } catch (IOException ex) { return false; } } return delegate.canWrite(); } /** * Like the super class implementation, but is aware of archive * files in its path. * For entries in a archive file, this is effectively a no-op: * The method will only return {@code true} if the entry isExisting and the * archive file was mounted read only. *

* This file system operation is virtually atomic. */ @Override public boolean setReadOnly() { if (null != innerArchive) { try { innerArchive.getController().setReadOnly(getInnerFsEntryName()); return true; } catch (IOException ex) { return false; } } return delegate.setReadOnly(); } /** * Returns the (uncompressed) length of the file. * The length returned of a valid archive file is {@code 0} in order * to properly emulate virtual directories across all platforms. *

* For archive file validation its virtual file system gets mounted. * In case a RAES encrypted ZIP file gets mounted, the user gets prompted * for its password unless the default configuration for key management * hasn't been overridden. *

* This file system operation is virtually atomic. * * @see Detecting Archive Paths and False Positives */ @Override public long length() { if (null != innerArchive) { final FsEntry entry; try { entry = innerArchive.getController().getEntry(getInnerFsEntryName()); } catch (IOException ex) { return 0; } if (null == entry || entry.isType(DIRECTORY)) return 0; final long length = entry.getSize(DATA); return length >= 0 ? length : 0; } return delegate.length(); } /** * Returns a {@code long} value representing the time this file was * last modified, measured in milliseconds since the epoch (00:00:00 GMT, * January 1, 1970), or {@code 0L} if the file does not exist or if an * I/O error occurs or if this is a ghost directory in an archive file. *

* This file system operation is virtually atomic. * * @see Package description for more information * about ghost directories */ @Override public long lastModified() { if (null != innerArchive) { final FsEntry entry; try { entry = innerArchive.getController().getEntry(getInnerFsEntryName()); } catch (IOException ex) { return 0; } if (null == entry) return 0; final long time = entry.getTime(Access.WRITE); return 0 <= time ? time : 0; } return delegate.lastModified(); } /** * Sets the last modification of this file or (virtual) directory. * If this is a ghost directory within an archive file, it's reincarnated * as a plain directory within the archive file. *

* Note that calling this method may incur a severe performance penalty * if the file is an entry in an archive file which has just been written * (such as after a normal copy operation). * If you want to copy a file's contents as well as its last modification * time, use {@link #cp_p(File, File)} instead. *

* This file system operation is virtually atomic. * * @see #cp_p(File, File) * @see Package description for more information * about ghost directories */ @Override public boolean setLastModified(final long time) { if (null != innerArchive) { try { innerArchive.getController() .setTime(getInnerFsEntryName(), BitField.of(Access.WRITE), time); return true; } catch (IOException ex) { return false; } } return delegate.setLastModified(time); } /** * Returns the names of the members in this directory in a newly * created array. * The returned array is not sorted. * This is the most efficient list method. *

* Note: Archive entries with absolute paths are ignored by * this method and are never returned. *

* This file system operation is virtually atomic. */ @Override public @Nullable String[] list() { if (null != innerArchive) { final FsEntry entry; try { entry = innerArchive.getController().getEntry(getInnerFsEntryName()); } catch (IOException ex) { return null; } if (null == entry) return null; final Set members = entry.getMembers(); return null == members ? null : members.toArray(new String[members.size()]); } return delegate.list(); } /** * Returns the names of the members in this directory which are * accepted by {@code filenameFilter} in a newly created array. * The returned array is not sorted. *

* Note: Archive entries with absolute paths are ignored by * this method and are never returned. *

* This file system operation is virtually atomic. * * @return {@code null} if this is not a directory or an archive file, * a valid (but maybe empty) array otherwise. */ @Override public @Nullable String[] list(final @CheckForNull FilenameFilter filter) { if (null != innerArchive) { final FsEntry entry; try { entry = innerArchive.getController().getEntry(getInnerFsEntryName()); } catch (IOException ex) { return null; } if (null == entry) return null; final Set members = entry.getMembers(); if (null == members) return null; if (null == filter) return members.toArray(new String[members.size()]); final Collection filtered = new ArrayList(members.size()); for (final String member : members) if (filter.accept(this, member)) filtered.add(member); return filtered.toArray(new String[filtered.size()]); } return delegate.list(filter); } /** * Equivalent to {@link #listFiles(FilenameFilter, TArchiveDetector) * listFiles((FilenameFilter) null, getArchiveDetector())}. */ @Override public @Nullable TFile[] listFiles() { return listFiles((FilenameFilter) null, detector); } /** * Returns {@code TFile} objects for the members in this directory * in a newly created array. * The returned array is not sorted. *

* Note that archive entries with absolute paths are ignored by this * method and are never returned. *

* This file system operation is virtually atomic. * * @param detector The archive detector to detect any archives files in * the member file names. * @return {@code null} if this is not a directory or an archive file, * a valid (but maybe empty) array otherwise. */ public @Nullable TFile[] listFiles(TArchiveDetector detector) { return listFiles((FilenameFilter) null, detector); } /** * Equivalent to {@link #listFiles(FilenameFilter, TArchiveDetector) * listFiles(filenameFilter, getArchiveDetector())}. */ @Override public @Nullable TFile[] listFiles( @CheckForNull FilenameFilter filter) { return listFiles(filter, detector); } /** * Returns {@code TFile} objects for the members in this directory * which are accepted by {@code filenameFilter} in a newly created * array. * The returned array is not sorted. *

* Note that archive entries with absolute paths are ignored by this * method and are never returned. *

* This file system operation is virtually atomic. * * @param filter the file filter. * @param detector the archive detector to detect any archives files in * the member file names. * @return {@code null} if this is not a directory or an archive file, * a valid (but maybe empty) array otherwise. */ public @Nullable TFile[] listFiles( final @CheckForNull FilenameFilter filter, final TArchiveDetector detector) { if (null != innerArchive) { final FsEntry entry; try { entry = innerArchive.getController().getEntry(getInnerFsEntryName()); } catch (IOException ex) { return null; } if (null == entry) return null; final Set members = entry.getMembers(); if (null == members) return null; final Collection filtered = new ArrayList(members.size()); for (final String member : members) if (filter == null || filter.accept(this, member)) filtered.add(new TFile(TFile.this, member, detector)); return filtered.toArray(new TFile[filtered.size()]); } return convert(delegate.listFiles(filter), detector); } private static @Nullable TFile[] convert( final File[] files, final TArchiveDetector detector) { if (null == files) return null; // no directory TFile[] results = new TFile[files.length]; for (int i = files.length; 0 <= --i; ) results[i] = new TFile(files[i], detector); return results; } /** * Equivalent to {@link #listFiles(FileFilter, TArchiveDetector) * listFiles(fileFilter, getArchiveDetector())}. */ @Override public @Nullable TFile[] listFiles(@CheckForNull FileFilter filter) { return listFiles(filter, detector); } /** * Returns {@code TFile} objects for the members in this directory * which are accepted by {@code fileFilter} in a newly created array. * The returned array is not sorted. *

* Note that archive entries with absolute paths are ignored by this * method and are never returned. *

* This file system operation is virtually atomic. * * @param filter the file filter. * @param detector The archive detector to detect any archives files in * the member file names. * @return {@code null} if this is not a directory or an archive file, * a valid (but maybe empty) array otherwise. */ public @Nullable TFile[] listFiles( final @CheckForNull FileFilter filter, final TArchiveDetector detector) { if (null != innerArchive) { final FsEntry entry; try { entry = innerArchive.getController().getEntry(getInnerFsEntryName()); } catch (IOException ex) { return null; } if (null == entry) return null; final Set members = entry.getMembers(); if (null == members) return null; final Collection filtered = new ArrayList(members.size()); for (final String member : members) { final TFile file = new TFile(this, member, detector); if (filter == null || filter.accept(file)) filtered.add(file); } return filtered.toArray(new TFile[filtered.size()]); } return delegateListFiles(filter, detector); } private @Nullable TFile[] delegateListFiles( final @CheckForNull FileFilter filter, final TArchiveDetector detector) { // When filtering, we want to pass in {@code de.schlichtherle.truezip.io.TFile} // objects rather than {@code File} objects, so we cannot // just call {@code entry.listFiles(FileFilter)}. // Instead, we will query the entry for the children names (i.e. // Strings) only, construct {@code de.schlichtherle.truezip.io.TFile} // instances from this and then apply the filter to construct the // result list. final List filteredList = new ArrayList(); final String[] members = delegate.list(); if (members == null) return null; // no directory for (int i = 0, l = members.length; i < l; i++) { final String member = members[i]; final TFile file = new TFile(this, member, detector); if (filter == null || filter.accept(file)) filteredList.add(file); } final TFile[] list = new TFile[filteredList.size()]; filteredList.toArray(list); return list; } /** * Creates a new, empty file similar to its superclass implementation. * Note that this method doesn't create archive files because archive * files are virtual directories, not files! *

* This file system operation is virtually atomic. * * @see #mkdir */ @Override public boolean createNewFile() throws IOException { if (null != innerArchive) { final FsController controller = innerArchive.getController(); final FsEntryName entryName = getInnerFsEntryName(); // This is not really atomic, but should be OK in this case. if (null != controller.getEntry(entryName)) return false; controller.mknod( entryName, FILE, BitField.of(EXCLUSIVE).set(CREATE_PARENTS, isLenient()), null); return true; } return delegate.createNewFile(); } /** {@inheritDoc} */ @Override public boolean mkdirs() { if (null == innerArchive) return delegate.mkdirs(); final TFile parent = getParentFile(); if (null != parent && !parent.exists()) parent.mkdirs(); // TODO: Profile: return parent.isDirectory() && mkdir(); // May perform better in certain situations where (probably false // positive) archive files are involved. return mkdir(); } /** * Creates a new, empty (virtual) directory similar to its superclass * implementation. * This method creates an archive file if {@link #isArchive} returns * {@code true}. * Example: * {@code new TFile("archive.zip").mkdir();} *

* Alternatively, archive files get created automatically by simply * creating their entries. * Example: * {@code new TFileOutputStream("archive.zip/README");} * This assumes the default configuration where {@link #isLenient} returns * {@code true}. *

* This file system operation is virtually atomic. */ @Override public boolean mkdir() { if (null != innerArchive) { try { innerArchive.getController().mknod( getInnerFsEntryName(), DIRECTORY, BitField.of(EXCLUSIVE) // redundant for directory entries .set(CREATE_PARENTS, isLenient()), null); return true; } catch (IOException ex) { return false; } } return delegate.mkdir(); } /** * Ensures that a (virtual) directory with {@link #getPath() this path name} * exists in the (federated) file system. * * @param recursive whether or not any missing ancestor directories shall * get created if required. * @return {@code this} * @throws IOException if any I/O error occurs. */ public TFile mkdir(final boolean recursive) throws IOException { if (null != innerArchive) { if (recursive) { final TFile parent = getParentFile(); if (null != parent && !parent.exists()) parent.mkdir(recursive); } final FsController controller = innerArchive.getController(); final FsEntryName innerEntryName = getInnerFsEntryName(); try { controller.mknod( innerEntryName, DIRECTORY, BitField.of(EXCLUSIVE) // redundant for directory entries .set(CREATE_PARENTS, isLenient()), null); } catch (IOException ex) { final FsEntry entry = controller.getEntry(innerEntryName); if (null == entry || !entry.isType(DIRECTORY)) throw ex; } } else { final File dir = delegate; if (!(recursive ? dir.mkdirs() : dir.mkdir()) && !dir.isDirectory()) throw new IOException(dir + " (cannot create directory)"); } return this; } /** * {@inheritDoc} * * @deprecated This method just returns a boolean value to indicate failure, * which is hard to analyze. * @see #rm() */ @Deprecated @Override public boolean delete() { try { rm(this); return true; } catch (IOException ex) { return false; } } /** * Equivalent to {@link #rm(File) rm(this)}. * * @return {@code this} * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods */ public TFile rm() throws IOException { rm(this); return this; } /** * Deletes the given file or directory. * If the file is a directory, it must be empty. *

* This file system operation is virtually atomic. * * @param node the file or directory. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods */ public static void rm(File node) throws IOException { if (node instanceof TFile) { TFile file = (TFile) node; if (null != file.innerArchive) { file.innerArchive.getController().unlink(file.getInnerFsEntryName()); return; } node = file.delegate; } if (!node.delete()) throw new IOException(node + " (cannot delete)"); } /** * Equivalent to {@link #rm_r(File) rm_r(this)}. * * @return {@code this} * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods * @see Traversing Directory Trees */ public TFile rm_r() throws IOException { TBIO.rm_r(this, detector); return this; } /** * Recursively deletes the given file or directory tree. *

* If {@code node} is an instance of this * class, its {@link #getArchiveDetector() archive detector} * is used to detect prospective archive files in the directory tree. * Otherwise, * {@link TArchiveDetector#NULL} * is used to detect prospective archive files in the directory tree. *

* This file system operation is not atomic. * * @param node the file or directory tree. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods * @see Traversing Directory Trees */ public static void rm_r(File node) throws IOException { TBIO.rm_r(node, node instanceof TFile ? ((TFile) node).detector : TArchiveDetector.NULL); } @Override public void deleteOnExit() { if (innerArchive != null) { // Support for this operation for archive files and entries has been // removed in TrueZIP 7 because using a shutdown hook uncautiously // introduces a potential memory leak when using multiple class // loaders to load TrueZIP. throw new UnsupportedOperationException(); } delegate.deleteOnExit(); } /** * {@inheritDoc} * * @param dst the destination file or directory tree. * @return {@code true} on success or {@code false} otherwise. * @deprecated This method just returns a boolean value to indicate failure, * which is hard to analyze. * @see #mv(File) */ @Deprecated @Override public boolean renameTo(final File dst) { try { mv(this, dst, detector); return true; } catch (IOException ex) { return false; } } /** * Equivalent to {@link #mv(File, File, TArchiveDetector) mv(this, dst, getArchiveDetector())}. * * @param dst the destination file or directory tree. * @return {@code this} * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods * @see Traversing Directory Trees */ public TFile mv(File dst) throws IOException { mv(this, dst, detector); return this; } /** * Moves the given source file or directory to the given destination file * or directory. *

* In certain cases, this method might perform a recursive copy-then-delete * operation rather than an atomic move operation. * This method uses the given archive detector to detect any * archive files when traversing a directory tree. * * @param src the source file or directory tree. * @param dst the destination file or directory tree. * @param detector the archive detector to use for detecting any archive * files in the source directory tree. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods * @see Traversing Directory Trees */ public static void mv( final File src, final File dst, final TArchiveDetector detector) throws IOException { if (detector.toString().isEmpty()) { final boolean srcArchived; final File srcDelegate; if (src instanceof TFile) { final TFile srcFile = (TFile) src; srcArchived = null != srcFile.innerArchive; srcDelegate = srcFile.delegate; } else { srcArchived = false; srcDelegate = src; } final boolean dstArchived; final File dstDelegate; if (dst instanceof TFile) { final TFile dstFile = (TFile) dst; dstArchived = null != dstFile.innerArchive; dstDelegate = dstFile.delegate; } else { dstArchived = false; dstDelegate = src; } if (!srcArchived && !dstArchived) if (srcDelegate.renameTo(dstDelegate)) return; else throw new IOException(src + " (cannot move to " + dst + ")"); } TBIO.mv(src, dst, detector); } /** * Copies the data from the input stream {@code in} to the output stream * {@code out} and closes both streams - even if an exception occurs. *

* This is a high performance implementation which uses a pooled background * thread to fill a FIFO of pooled buffers which is concurrently flushed by * the current thread. * It performs best when used with unbuffered streams. *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FeatureSupported
Preserves file attributesn/a
Copies directories recursivelyn/a
Reads and overwrites special filesn/a
Closes parameter streamsAlways
Direct Data Copying (DDC)n/a
Deletes partial written files on failuren/a
Deletes partial written directories on failuren/a
AtomicNo
* * @param in the input stream. * @param out the output stream. * @throws IOException if any I/O error occurs. * @see #cat(InputStream, OutputStream) * @see Bulk I/O Methods */ public static void cp(InputStream in, OutputStream out) throws IOException { Streams.copy(in, out); } /** * Copies the input stream {@code in} to the file {@code dst} and * closes the stream - even if an exception occurs. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FeatureSupported
Preserves file attributesNone
Copies directories recursivelyNo
Reads and overwrites special filesYes
Closes parameter streamsAlways
Direct Data Copying (DDC)No
Deletes partial written files on failureYes
Deletes partial written directories on failuren/a
AtomicNo
* * @param in the input stream. * @param dst the destination file. Note that although this just needs to * be a plain {@code File}, archive entries are only * supported for instances of this class. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods */ public static void cp(final InputStream in, final File dst) throws IOException { final OutputStream out = new TFileOutputStream(dst); try { cp(in, out); // always closes in and out } catch (IOException ex) { if (!dst.delete()) throw new IOException(dst + " (cannot delete)", ex); } } /** * Copies the file {@code src} to the output stream {@code out} and * closes the stream - even if an exception occurs. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FeatureSupported
Preserves file attributesNone
Copies directories recursivelyNo
Reads and overwrites special filesYes
Closes parameter streamsAlways
Direct Data Copying (DDC)No
Deletes partial written files on failuren/a
Deletes partial written directories on failuren/a
AtomicNo
* * @param src the source file. Note that although this just needs to * be a plain {@code File}, archive entries are only * supported for instances of this class. * @param out the output stream. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods */ public static void cp(File src, OutputStream out) throws IOException { cp(new TFileInputStream(src), out); // always closes in and out } /** * Equivalent to {@link #cp(File, File) cp(this, dst)}. * * @param dst the destination file. * @return {@code this} * @throws IOException if any I/O error occurs. */ public TFile cp(File dst) throws IOException { TBIO.cp(false, this, dst); return this; } /** * Copies the file {@code src} to the file {@code dst}. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FeatureSupported
Preserves file attributesNone
Copies directories recursivelyNo
Reads and overwrites special filesYes
Closes parameter streamsn/a
Direct Data Copying (DDC)Yes
Deletes partial written files on failureYes
Deletes partial written directories on failuren/a
AtomicNo
* * @param src the source file. Note that although this just needs to * be a plain {@code File}, archive entries are only * supported for instances of this class. * @param dst the destination file. Note that although this just needs to * be a plain {@code File}, archive entries are only * supported for instances of this class. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods */ public static void cp(File src, File dst) throws IOException { TBIO.cp(false, src, dst); } /** * Equivalent to {@link #cp_p(File, File) cp_p(this, dst)}. * * @param dst the destination file. * @return {@code this} * @throws IOException if any I/O error occurs. */ public TFile cp_p(File dst) throws IOException { TBIO.cp(true, this, dst); return this; } /** * Copies the file {@code src} to the file {@code dst} and tries to * preserve all attributes of the source file to the destination file, too. * Currently, only the last modification time is preserved. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FeatureSupported
Preserves file attributesBest effort
Copies directories recursivelyNo
Reads and overwrites special filesYes
Closes parameter streamsn/a
Direct Data Copying (DDC)Yes
Deletes partial written files on failureYes
Deletes partial written directories on failuren/a
AtomicNo
* * @param src the source file. Note that although this just needs to * be a plain {@code File}, archive entries are only * supported for instances of this class. * @param dst the destination file. Note that although this just needs to * be a plain {@code File}, archive entries are only * supported for instances of this class. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods */ public static void cp_p(File src, File dst) throws IOException { TBIO.cp(true, src, dst); } /** * Equivalent to {@link #cp_r(File, File, TArchiveDetector, TArchiveDetector) cp_r(this, dst, getArchiveDetector(), getArchiveDetector())}. * * @param dst the destination file or directory tree. * @return {@code this} * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods * @see Traversing Directory Trees */ public TFile cp_r(File dst) throws IOException { TBIO.cp_r(false, this, dst, detector, detector); return this; } /** * Recursively copies the file or directory {@code src} * to the file or directory {@code dst}. *

* This method uses the given archive detectors to detect any * archive files in the source and destination directory trees. *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FeatureSupported
Preserves file attributesNone
Copies directories recursivelyYes
Reads and overwrites special filesNo
Closes parameter streamsn/a
Direct Data Copying (DDC)Yes
Deletes partial written files on failureYes
Deletes partial written directories on failureNo
AtomicNo
* * @param src the source file. Note that although this just needs to * be a plain {@code File}, archive files and entries * are only supported for instances of this class. * @param dst the destination file. Note that although this just needs to * be a plain {@code File}, archive files and entries * are only supported for instances of this class. * @param srcDetector the archive detector to use for detecting any * archive files in the source directory tree. * @param dstDetector the archive detector to use for detecting any * archive files in the destination directory tree. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods * @see Traversing Directory Trees */ public static void cp_r(File src, File dst, TArchiveDetector srcDetector, TArchiveDetector dstDetector) throws IOException { TBIO.cp_r(false, src, dst, srcDetector, dstDetector); } /** * Equivalent to {@link #cp_rp(File, File, TArchiveDetector, TArchiveDetector) cp_rp(this, dst, getArchiveDetector(), getArchiveDetector())}. * * @param dst the destination file or directory tree. * @return {@code this} * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods * @see Traversing Directory Trees */ public TFile cp_rp(File dst) throws IOException { TBIO.cp_r(true, this, dst, detector, detector); return this; } /** * Recursively copies the file or directory {@code src} to the file or * directory {@code dst} and tries to preserve all attributes of each * source file to the destination file, too. * Note that the current implementation only preserves the last * modification time. *

* This method uses the given archive detectors to detect any * archive files when traversing a directory tree. *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FeatureSupported
Preserves file attributesBest effort
Copies directories recursivelyYes
Reads and overwrites special filesNo
Closes parameter streamsn/a
Direct Data Copying (DDC)Yes
Deletes partial written files on failureYes
Deletes partial written directories on failureNo
AtomicNo
* * @param src the source file. Note that although this just needs to * be a plain {@code File}, archive files and entries * are only supported for instances of this class. * @param dst the destination file. Note that although this just needs to * be a plain {@code File}, archive files and entries * are only supported for instances of this class. * @param srcDetector the archive detector to use for detecting any * archive files in the source directory tree. * @param dstDetector the archive detector to use for detecting any * archive files in the destination directory tree. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods * @see Traversing Directory Trees */ public static void cp_rp( File src, File dst, TArchiveDetector srcDetector, TArchiveDetector dstDetector) throws IOException { TBIO.cp_r(true, src, dst, srcDetector, dstDetector); } /** * Copies the input stream {@code in} to this file or entry in an archive * file * without closing it. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FeatureSupported
Preserves file attributesn/a
Copies directories recursivelyn/a
Reads and overwrites special filesYes
Closes parameter streamsNever
Direct Data Copying (DDC)n/a
Deletes partial written files on failureYes
Deletes partial written directories on failuren/a
AtomicNo
* * @param in the input stream. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods */ public void input(final InputStream in) throws IOException { final OutputStream out = new TFileOutputStream(this, false); try { try { Streams.cat(in, out); } finally { out.close(); } } catch (IOException ex) { delete(); throw ex; } } /** * Copies this file or entry in an archive file to the output stream * {@code out} * without closing it. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FeatureSupported
Preserves file attributesn/a
Copies directories recursivelyn/a
Reads and overwrites special filesYes
Closes parameter streamsNever
Direct Data Copying (DDC)n/a
Deletes partial written files on failuren/a
Deletes partial written directories on failuren/a
AtomicNo
* * @param out the output stream. * @throws IOException if any I/O error occurs. * @see Bulk I/O Methods */ public void output(final OutputStream out) throws IOException { final InputStream in = new TFileInputStream(this); try { Streams.cat(in, out); } finally { in.close(); } } /** * Copies the data from the given input stream to the given output stream * without closing them. * The name of this method is inspired by the Unix command line utility * {@code cat} because you could use it to concatenate the * contents of multiple streams. *

* This is a high performance implementation which uses a pooled background * thread to fill a FIFO of data buffers which is concurrently flushed by * the current thread. * It performs best when used with unbuffered streams. *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FeatureSupported
Preserves file attributesn/a
Copies directories recursivelyn/a
Reads and overwrites special filesn/a
Closes parameter streamsNever
Direct Data Copying (DDC)n/a
Deletes partial written files on failuren/a
Deletes partial written directories on failuren/a
AtomicNo
* * @param in the input stream. * @param out the output stream. * @throws IOException if any I/O error occurs. * @see #cp(InputStream, OutputStream) * @see Bulk I/O Methods */ public static void cat(final InputStream in, final OutputStream out) throws IOException { Streams.cat(in, out); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy