diff options
Diffstat (limited to 'src/main/java/de/waldheinz/fs/fat/ClusterChain.java')
-rw-r--r-- | src/main/java/de/waldheinz/fs/fat/ClusterChain.java | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/src/main/java/de/waldheinz/fs/fat/ClusterChain.java b/src/main/java/de/waldheinz/fs/fat/ClusterChain.java new file mode 100644 index 0000000..e296092 --- /dev/null +++ b/src/main/java/de/waldheinz/fs/fat/ClusterChain.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de> + * + * This library 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 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 library; If not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package de.waldheinz.fs.fat; + +import de.waldheinz.fs.AbstractFsObject; +import de.waldheinz.fs.BlockDevice; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * A chain of clusters as stored in a {@link Fat}. + * + * @author Matthias Treydte <waldheinz at gmail.com> + */ +final class ClusterChain extends AbstractFsObject { + protected final Fat fat; + private final BlockDevice device; + private final int clusterSize; + protected final long dataOffset; + + private long startCluster; + + /** + * Creates a new {@code ClusterChain} that contains no clusters. + * + * @param fat the {@code Fat} that holds the new chain + * @param readOnly if the chain should be created read-only + */ + public ClusterChain(Fat fat, boolean readOnly) { + this(fat, 0, readOnly); + } + + public ClusterChain(Fat fat, long startCluster, boolean readOnly) { + super(readOnly); + + this.fat = fat; + + if (startCluster != 0) { + this.fat.testCluster(startCluster); + + if (this.fat.isFreeCluster(startCluster)) + throw new IllegalArgumentException( + "cluster " + startCluster + " is free"); + } + + this.device = fat.getDevice(); + this.dataOffset = FatUtils.getFilesOffset(fat.getBootSector()); + this.startCluster = startCluster; + this.clusterSize = fat.getBootSector().getBytesPerCluster(); + } + + public int getClusterSize() { + return clusterSize; + } + + public Fat getFat() { + return fat; + } + + public BlockDevice getDevice() { + return device; + } + + /** + * Returns the first cluster of this chain. + * + * @return the chain's first cluster, which may be 0 if this chain does + * not contain any clusters + */ + public long getStartCluster() { + return startCluster; + } + + /** + * Calculates the device offset (0-based) for the given cluster and offset + * within the cluster. + * + * @param cluster + * @param clusterOffset + * @return long + * @throws FileSystemException + */ + private long getDevOffset(long cluster, int clusterOffset) { + return dataOffset + clusterOffset + + ((cluster - Fat.FIRST_CLUSTER) * clusterSize); + } + + /** + * Returns the size this {@code ClusterChain} occupies on the device. + * + * @return the size this chain occupies on the device in bytes + */ + public long getLengthOnDisk() { + if (getStartCluster() == 0) return 0; + + return getChainLength() * clusterSize; + } + + /** + * Sets the length of this {@code ClusterChain} in bytes. Because a + * {@code ClusterChain} can only contain full clusters, the new size + * will always be a multiple of the cluster size. + * + * @param size the desired number of bytes the can be stored in + * this {@code ClusterChain} + * @return the true number of bytes this {@code ClusterChain} can contain + * @throws IOException on error setting the new size + * @see #setChainLength(int) + */ + public long setSize(long size) throws IOException { + final long nrClusters = ((size + clusterSize - 1) / clusterSize); + if (nrClusters > Integer.MAX_VALUE) + throw new IOException("too many clusters"); + + setChainLength((int) nrClusters); + + return clusterSize * nrClusters; + } + + /** + * Determines the length of this {@code ClusterChain} in clusters. + * + * @return the length of this chain + */ + public int getChainLength() { + if (getStartCluster() == 0) return 0; + + final long[] chain = getFat().getChain(getStartCluster()); + return chain.length; + } + + /** + * Sets the length of this cluster chain in clusters. + * + * @param nrClusters the new number of clusters this chain should contain, + * must be {@code >= 0} + * @throws IOException on error updating the chain length + * @see #setSize(long) + */ + public void setChainLength(int nrClusters) throws IOException { + if (nrClusters < 0) throw new IllegalArgumentException( + "negative cluster count"); //NOI18N + + if ((this.startCluster == 0) && (nrClusters == 0)) { + /* nothing to do */ + } else if ((this.startCluster == 0) && (nrClusters > 0)) { + final long[] chain = fat.allocNew(nrClusters); + this.startCluster = chain[0]; + } else { + final long[] chain = fat.getChain(startCluster); + + if (nrClusters != chain.length) { + if (nrClusters > chain.length) { + /* grow the chain */ + int count = nrClusters - chain.length; + + while (count > 0) { + fat.allocAppend(getStartCluster()); + count--; + } + } else { + /* shrink the chain */ + if (nrClusters > 0) { + fat.setEof(chain[nrClusters - 1]); + for (int i = nrClusters; i < chain.length; i++) { + fat.setFree(chain[i]); + } + } else { + for (int i=0; i < chain.length; i++) { + fat.setFree(chain[i]); + } + + this.startCluster = 0; + } + } + } + } + } + + public void readData(long offset, ByteBuffer dest) + throws IOException { + + int len = dest.remaining(); + + if ((startCluster == 0 && len > 0)) throw new EOFException(); + + final long[] chain = getFat().getChain(startCluster); + final BlockDevice dev = getDevice(); + + int chainIdx = (int) (offset / clusterSize); + if (offset % clusterSize != 0) { + int clusOfs = (int) (offset % clusterSize); + int size = Math.min(len, + (int) (clusterSize - (offset % clusterSize) - 1)); + dest.limit(dest.position() + size); + + dev.read(getDevOffset(chain[chainIdx], clusOfs), dest); + + offset += size; + len -= size; + chainIdx++; + } + + while (len > 0) { + int size = Math.min(clusterSize, len); + dest.limit(dest.position() + size); + + dev.read(getDevOffset(chain[chainIdx], 0), dest); + + len -= size; + chainIdx++; + } + } + + /** + * Writes data to this cluster chain, possibly growing the chain so it + * can store the additional data. When this method returns without throwing + * an exception, the buffer's {@link ByteBuffer#position() position} will + * equal it's {@link ByteBuffer#limit() limit}, and the limit will not + * have changed. This is not guaranteed if writing fails. + * + * @param offset the offset where to write the first byte from the buffer + * @param srcBuf the buffer to write to this {@code ClusterChain} + * @throws IOException on write error + */ + public void writeData(long offset, ByteBuffer srcBuf) throws IOException { + + int len = srcBuf.remaining(); + + if (len == 0) return; + + final long minSize = offset + len; + if (getLengthOnDisk() < minSize) { + setSize(minSize); + } + + final long[] chain = fat.getChain(getStartCluster()); + + int chainIdx = (int) (offset / clusterSize); + if (offset % clusterSize != 0) { + int clusOfs = (int) (offset % clusterSize); + int size = Math.min(len, + (int) (clusterSize - (offset % clusterSize))); + srcBuf.limit(srcBuf.position() + size); + + device.write(getDevOffset(chain[chainIdx], clusOfs), srcBuf); + + offset += size; + len -= size; + chainIdx++; + } + + while (len > 0) { + int size = Math.min(clusterSize, len); + srcBuf.limit(srcBuf.position() + size); + + device.write(getDevOffset(chain[chainIdx], 0), srcBuf); + + len -= size; + chainIdx++; + } + + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (!(obj instanceof ClusterChain)) return false; + + final ClusterChain other = (ClusterChain) obj; + + if (this.fat != other.fat && + (this.fat == null || !this.fat.equals(other.fat))) { + + return false; + } + + if (this.startCluster != other.startCluster) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 79 * hash + + (this.fat != null ? this.fat.hashCode() : 0); + hash = 79 * hash + + (int) (this.startCluster ^ (this.startCluster >>> 32)); + return hash; + } + +} |