/* * Copyright (C) 2009,2010 Matthias Treydte * * 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; } }