/** * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.commons.file.archive; import com.mucommander.commons.file.AbstractFile; import com.mucommander.commons.file.FileOperation; import com.mucommander.commons.file.FilePermissions; import com.mucommander.commons.file.FileURL; import com.mucommander.commons.file.PermissionAccess; import com.mucommander.commons.file.PermissionType; import com.mucommander.commons.file.UnsupportedFileOperation; import com.mucommander.commons.file.UnsupportedFileOperationException; import com.mucommander.commons.file.filter.FileFilter; import com.mucommander.commons.file.filter.FilenameFilter; import com.mucommander.commons.io.ByteUtils; import com.mucommander.commons.io.RandomAccessInputStream; import com.mucommander.commons.io.RandomAccessOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * <code>AbstractArchiveEntryFile</code> represents a file entry inside an archive. * An <code>AbstractArchiveEntryFile</code> is always associated with an {@link ArchiveEntry} object which contains * information about the entry (name, size, date, ...) and with an {@link AbstractArchiveFile} which acts as an entry * repository and provides operations such as listing a directory entry's files, adding or removing entries * (if the archive is writable), etc... * * <p> * <code>AbstractArchiveEntryFile</code> implements {@link com.mucommander.commons.file.AbstractFile} by delegating methods to * the <code>ArchiveEntry</code> and <code>AbstractArchiveFile</code> instances. * <code>AbstractArchiveEntryFile</code> is agnostic to the actual archive format. In other words, there is no need to * extend this class for a particular archive format, <code>ArchiveEntry</code> and <code>AbstractArchiveFile</code> * provide a generic framework that isolates from the archive format's specifics. * </p> * <p> * This class is abstract (as the name implies) and implemented by two subclasses: * <ul> * <li>{@link ROArchiveEntryFile}: represents an entry inside a read-only archive</li> * <li>{@link RWArchiveEntryFile}: represents an entry inside a {@link AbstractArchiveFile#isWritable() read-write} archive</li> * </ul> * </p> * * @see AbstractArchiveFile * @see ArchiveEntry * @author Maxence Bernard */ public abstract class AbstractArchiveEntryFile extends AbstractFile { /** The archive file that contains this entry */ protected AbstractArchiveFile archiveFile; /** This entry file's parent, can be the archive file itself if this entry is located at the top level */ protected AbstractFile parent; /** The ArchiveEntry object that contains information about this entry */ protected ArchiveEntry entry; /** * Creates a new AbstractArchiveEntryFile. * * @param url the FileURL instance that represents this file's location * @param archiveFile the AbstractArchiveFile instance that contains this entry * @param entry the ArchiveEntry object that contains information about this entry */ protected AbstractArchiveEntryFile(FileURL url, AbstractArchiveFile archiveFile, ArchiveEntry entry) { super(url); this.archiveFile = archiveFile; this.entry = entry; } /** * Returns the ArchiveEntry instance that contains information about the archive entry (path, size, date, ...). * * @return the ArchiveEntry instance that contains information about the archive entry (path, size, date, ...) */ public ArchiveEntry getEntry() { return entry; } /** * Returns the {@link AbstractArchiveFile} that contains the entry represented by this file. * * @return the AbstractArchiveFile that contains the entry represented by this file */ public AbstractArchiveFile getArchiveFile() { return archiveFile; } /** * Returns the relative path of this entry, with respect to the archive file. The path separator of the returned * path is the one returned by {@link #getSeparator()}. As a relative path, the returned path does not start * with a separator character. * * @return the relative path of this entry, with respect to the archive file. */ public String getRelativeEntryPath() { String path = entry.getPath(); // Replace all occurrences of the entry's separator by the archive file's separator, only if the separator is // not "/" (i.e. the entry path separator). String separator = getSeparator(); if(!separator.equals("/")) path = path.replace("/", separator); return path; } ///////////////////////////////// // AbstractFile implementation // ///////////////////////////////// @Override public long getDate() { return entry.getDate(); } @Override public long getSize() { return entry.getSize(); } @Override public boolean isDirectory() { return entry.isDirectory(); } @Override public boolean isArchive() { // Archive entries files may be wrapped by archive files but they are not archive files per se return false; } @Override public AbstractFile[] ls() throws IOException, UnsupportedFileOperationException { return archiveFile.ls(this, null, null); } @Override public AbstractFile[] ls(FilenameFilter filter) throws IOException, UnsupportedFileOperationException { return archiveFile.ls(this, filter, null); } @Override public AbstractFile[] ls(FileFilter filter) throws IOException, UnsupportedFileOperationException { return archiveFile.ls(this, null, filter); } @Override public AbstractFile getParent() { return parent; } @Override public void setParent(AbstractFile parent) { this.parent = parent; } /** * Returns <code>true</code> if this entry exists within the archive file. * * @return true if this entry exists within the archive file */ @Override public boolean exists() { return entry.exists(); } @Override public FilePermissions getPermissions() { // Return the entry's permissions return entry.getPermissions(); } @Override public void changePermission(PermissionAccess access, PermissionType permission, boolean enabled) throws IOException, UnsupportedFileOperationException { changePermissions(ByteUtils.setBit(getPermissions().getIntValue(), (permission.toInt() << (access.toInt()*3)), enabled)); } @Override public String getOwner() { return entry.getOwner(); } @Override public boolean canGetOwner() { return entry.getOwner()!=null; } @Override public String getGroup() { return entry.getGroup(); } @Override public boolean canGetGroup() { return entry.getGroup()!=null; } /** * Always returns <code>false</code>. */ @Override public boolean isSymlink() { return false; } /** * Always returns <code>false</code>. */ @Override public boolean isSystem() { return false; } /** * Delegates to the archive file's {@link AbstractArchiveFile#getFreeSpace()} method. * * @throws IOException if an I/O error occurred * @throws UnsupportedFileOperationException if the underlying archive file does not support * {@link FileOperation#GET_FREE_SPACE} operations. */ @Override public long getFreeSpace() throws IOException, UnsupportedFileOperationException { return archiveFile.getFreeSpace(); } /** * Delegates to the archive file's {@link AbstractArchiveFile#getTotalSpace()} method. * * @throws IOException if an I/O error occurred * @throws UnsupportedFileOperationException if the underlying archive file does not support * {@link FileOperation#GET_TOTAL_SPACE} operations. */ @Override public long getTotalSpace() throws IOException, UnsupportedFileOperationException { return archiveFile.getTotalSpace(); } /** * Delegates to the archive file's {@link AbstractArchiveFile#getEntryInputStream(ArchiveEntry,ArchiveEntryIterator)}} * method. * * @throws UnsupportedFileOperationException if the underlying archive file does not support * {@link FileOperation#READ_FILE} operations. */ @Override public InputStream getInputStream() throws IOException, UnsupportedFileOperationException { return archiveFile.getEntryInputStream(entry, null); } /** * Always throws an {@link UnsupportedFileOperationException}: append is not available for archive entries. * * @throws UnsupportedFileOperationException always */ @Override @UnsupportedFileOperation public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException { throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE); } /** * Always throws an {@link UnsupportedFileOperationException}: random read access is not available for archive * entries. * * @throws UnsupportedFileOperationException always */ @Override @UnsupportedFileOperation public RandomAccessInputStream getRandomAccessInputStream() throws UnsupportedFileOperationException { throw new UnsupportedFileOperationException(FileOperation.RANDOM_READ_FILE); } /** * Always throws an {@link UnsupportedFileOperationException}: random write access is not available for archive * entries. * * @throws UnsupportedFileOperationException always */ @Override @UnsupportedFileOperation public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException { throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE); } /** * Always throws an {@link UnsupportedFileOperationException} when called. * * @throws UnsupportedFileOperationException always */ @Override @UnsupportedFileOperation public void copyRemotelyTo(AbstractFile destFile) throws UnsupportedFileOperationException { // TODO: we could consider adding remote copy support to RWArchiveEntryFile throw new UnsupportedFileOperationException(FileOperation.COPY_REMOTELY); } /** * Always throws an {@link UnsupportedFileOperationException} when called. * * @throws UnsupportedFileOperationException always */ @Override @UnsupportedFileOperation public void renameTo(AbstractFile destFile) throws UnsupportedFileOperationException { // TODO: we could consider adding renaming support to RWArchiveEntryFile throw new UnsupportedFileOperationException(FileOperation.RENAME); } /** * Returns the same ArchiveEntry instance as {@link #getEntry()}. */ @Override public Object getUnderlyingFileObject() { return entry; } //////////////////////// // Overridden methods // //////////////////////// /** * This method is overridden to return the separator of the {@link #getArchiveFile() archive file} that contains * this entry. * * @return the separator of the archive file that contains this entry */ @Override public String getSeparator() { return archiveFile.getSeparator(); } /** * This method is overridden to use the archive file's absolute path as the base path of this entry file. */ @Override public String getAbsolutePath() { // Use the archive file's absolute path and append the entry's relative path to it return archiveFile.getAbsolutePath(true)+getRelativeEntryPath(); } /** * This method is overridden to use the archive file's canonical path as the base path of this entry file. */ @Override public String getCanonicalPath() { // Use the archive file's canonical path and append the entry's relative path to it return archiveFile.getCanonicalPath(true)+getRelativeEntryPath(); } /** * This method is overridden to return the archive's root folder. */ @Override public AbstractFile getRoot() { return archiveFile.getRoot(); } /** * This method is overridden to blindly return <code>false</code>, an archive entry cannot be a root folder. * * @return <code>false</code>, always */ @Override public boolean isRoot() { return false; } /** * This method is overridden to return the archive's volume folder. */ @Override public AbstractFile getVolume() { return archiveFile.getVolume(); } }