summaryrefslogtreecommitdiff
path: root/src/main/java/de/waldheinz/fs/fat/AbstractDirectory.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/waldheinz/fs/fat/AbstractDirectory.java')
-rw-r--r--src/main/java/de/waldheinz/fs/fat/AbstractDirectory.java384
1 files changed, 384 insertions, 0 deletions
diff --git a/src/main/java/de/waldheinz/fs/fat/AbstractDirectory.java b/src/main/java/de/waldheinz/fs/fat/AbstractDirectory.java
new file mode 100644
index 0000000..d3445b7
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/AbstractDirectory.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 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 java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This is the abstract base class for all directory implementations.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+abstract class AbstractDirectory {
+
+ /**
+ * The maximum length of the volume label.
+ *
+ * @see #setLabel(java.lang.String)
+ */
+ public static final int MAX_LABEL_LENGTH = 11;
+
+ private final List<FatDirectoryEntry> entries;
+ private final boolean readOnly;
+ private final boolean isRoot;
+
+ private boolean dirty;
+ private int capacity;
+ private String volumeLabel;
+
+ /**
+ * Creates a new instance of {@code AbstractDirectory}.
+ *
+ * @param capacity the initial capacity of the new instance
+ * @param readOnly if the instance should be read-only
+ * @param isRoot if the new {@code AbstractDirectory} represents a root
+ * directory
+ */
+ protected AbstractDirectory(
+ int capacity, boolean readOnly, boolean isRoot) {
+
+ this.entries = new ArrayList<FatDirectoryEntry>();
+ this.capacity = capacity;
+ this.readOnly = readOnly;
+ this.isRoot = isRoot;
+ }
+
+ /**
+ * Gets called when the {@code AbstractDirectory} must read it's content
+ * off the backing storage. This method must always fill the buffer's
+ * remaining space with the bytes making up this directory, beginning with
+ * the first byte.
+ *
+ * @param data the {@code ByteBuffer} to fill
+ * @throws IOException on read error
+ */
+ protected abstract void read(ByteBuffer data) throws IOException;
+
+ /**
+ * Gets called when the {@code AbstractDirectory} wants to write it's
+ * contents to the backing storage. This method is expected to write the
+ * buffer's remaining data to the storage, beginning with the first byte.
+ *
+ * @param data the {@code ByteBuffer} to write
+ * @throws IOException on write error
+ */
+ protected abstract void write(ByteBuffer data) throws IOException;
+
+ /**
+ * Returns the number of the cluster where this directory is stored. This
+ * is important when creating the ".." entry in a sub-directory, as this
+ * entry must poing to the storage cluster of it's parent.
+ *
+ * @return this directory's storage cluster
+ */
+ protected abstract long getStorageCluster();
+
+ /**
+ * Gets called by the {@code AbstractDirectory} when it has determined that
+ * it should resize because the number of entries has changed.
+ *
+ * @param entryCount the new number of entries this directory needs to store
+ * @throws IOException on write error
+ * @throws DirectoryFullException if the FAT12/16 root directory is full
+ * @see #sizeChanged(long)
+ * @see #checkEntryCount(int)
+ */
+ protected abstract void changeSize(int entryCount)
+ throws DirectoryFullException, IOException;
+
+ /**
+ * Replaces all entries in this directory.
+ *
+ * @param newEntries the new directory entries
+ */
+ public void setEntries(List<FatDirectoryEntry> newEntries) {
+ if (newEntries.size() > capacity)
+ throw new IllegalArgumentException("too many entries");
+
+ this.entries.clear();
+ this.entries.addAll(newEntries);
+ }
+
+ /**
+ *
+ *
+ * @param newSize the new storage space for the directory in bytes
+ * @see #changeSize(int)
+ */
+ protected final void sizeChanged(long newSize) throws IOException {
+ final long newCount = newSize / FatDirectoryEntry.SIZE;
+ if (newCount > Integer.MAX_VALUE)
+ throw new IOException("directory too large");
+
+ this.capacity = (int) newCount;
+ }
+
+ public final FatDirectoryEntry getEntry(int idx) {
+ return this.entries.get(idx);
+ }
+
+ /**
+ * Returns the current capacity of this {@code AbstractDirectory}.
+ *
+ * @return the number of entries this directory can hold in its current
+ * storage space
+ * @see #changeSize(int)
+ */
+ public final int getCapacity() {
+ return this.capacity;
+ }
+
+ /**
+ * The number of entries that are currently stored in this
+ * {@code AbstractDirectory}.
+ *
+ * @return the current number of directory entries
+ */
+ public final int getEntryCount() {
+ return this.entries.size();
+ }
+
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ public final boolean isRoot() {
+ return this.isRoot;
+ }
+
+ /**
+ * Gets the number of directory entries in this directory. This is the
+ * number of "real" entries in this directory, possibly plus one if a
+ * volume label is set.
+ *
+ * @return the number of entries in this directory
+ */
+ public int getSize() {
+ return entries.size() + ((this.volumeLabel != null) ? 1 : 0);
+ }
+
+ /**
+ * Mark this directory as dirty.
+ */
+ protected final void setDirty() {
+ this.dirty = true;
+ }
+
+ /**
+ * Checks if this {@code AbstractDirectory} is a root directory.
+ *
+ * @throws UnsupportedOperationException if this is not a root directory
+ * @see #isRoot()
+ */
+ private void checkRoot() throws UnsupportedOperationException {
+ if (!isRoot()) {
+ throw new UnsupportedOperationException(
+ "only supported on root directories");
+ }
+ }
+
+ /**
+ * Mark this directory as not dirty.
+ */
+ private void resetDirty() {
+ this.dirty = false;
+ }
+
+ /**
+ * Flush the contents of this directory to the persistent storage
+ */
+ public void flush() throws IOException {
+
+ final ByteBuffer data = ByteBuffer.allocate(
+ getCapacity() * FatDirectoryEntry.SIZE);
+
+ for (int i=0; i < entries.size(); i++) {
+ final FatDirectoryEntry entry = entries.get(i);
+
+ if (entry != null) {
+ entry.write(data);
+ }
+ }
+
+ /* TODO: the label could be placed directly the dot entries */
+
+ if (this.volumeLabel != null) {
+ final FatDirectoryEntry labelEntry =
+ FatDirectoryEntry.createVolumeLabel(volumeLabel);
+
+ labelEntry.write(data);
+ }
+
+ if (data.hasRemaining()) {
+ FatDirectoryEntry.writeNullEntry(data);
+ }
+
+ data.flip();
+
+ write(data);
+ resetDirty();
+ }
+
+ protected final void read() throws IOException {
+ final ByteBuffer data = ByteBuffer.allocate(
+ getCapacity() * FatDirectoryEntry.SIZE);
+
+ read(data);
+ data.flip();
+
+ for (int i=0; i < getCapacity(); i++) {
+ final FatDirectoryEntry e =
+ FatDirectoryEntry.read(data, isReadOnly());
+
+ if (e == null) break;
+
+ if (e.isVolumeLabel()) {
+ if (!this.isRoot) throw new IOException(
+ "volume label in non-root directory");
+
+ this.volumeLabel = e.getVolumeLabel();
+ } else {
+ entries.add(e);
+ }
+ }
+ }
+
+ public void addEntry(FatDirectoryEntry e) throws IOException {
+ assert (e != null);
+
+ if (getSize() == getCapacity()) {
+ changeSize(getCapacity() + 1);
+ }
+
+ entries.add(e);
+ }
+
+ public void addEntries(FatDirectoryEntry[] entries)
+ throws IOException {
+
+ if (getSize() + entries.length > getCapacity()) {
+ changeSize(getSize() + entries.length);
+ }
+
+ this.entries.addAll(Arrays.asList(entries));
+ }
+
+ public void removeEntry(FatDirectoryEntry entry) throws IOException {
+ assert (entry != null);
+
+ this.entries.remove(entry);
+ changeSize(getSize());
+ }
+
+ /**
+ * Returns the volume label that is stored in this directory. Reading the
+ * volume label is only supported for the root directory.
+ *
+ * @return the volume label stored in this directory, or {@code null}
+ * @throws UnsupportedOperationException if this is not a root directory
+ * @see #isRoot()
+ */
+ public String getLabel() throws UnsupportedOperationException {
+ checkRoot();
+
+ return volumeLabel;
+ }
+
+ public FatDirectoryEntry createSub(Fat fat) throws IOException {
+ final ClusterChain chain = new ClusterChain(fat, false);
+ chain.setChainLength(1);
+
+ final FatDirectoryEntry entry = FatDirectoryEntry.create(true);
+ entry.setStartCluster(chain.getStartCluster());
+
+ final ClusterChainDirectory dir =
+ new ClusterChainDirectory(chain, false);
+
+ /* add "." entry */
+
+ final FatDirectoryEntry dot = FatDirectoryEntry.create(true);
+ dot.setShortName(ShortName.DOT);
+ dot.setStartCluster(dir.getStorageCluster());
+ copyDateTimeFields(entry, dot);
+ dir.addEntry(dot);
+
+ /* add ".." entry */
+
+ final FatDirectoryEntry dotDot = FatDirectoryEntry.create(true);
+ dotDot.setShortName(ShortName.DOT_DOT);
+ dotDot.setStartCluster(getStorageCluster());
+ copyDateTimeFields(entry, dotDot);
+ dir.addEntry(dotDot);
+
+ dir.flush();
+
+ return entry;
+ }
+
+ private static void copyDateTimeFields(
+ FatDirectoryEntry src, FatDirectoryEntry dst) {
+
+ dst.setCreated(src.getCreated());
+ dst.setLastAccessed(src.getLastAccessed());
+ dst.setLastModified(src.getLastModified());
+ }
+
+ /**
+ * Sets the volume label that is stored in this directory. Setting the
+ * volume label is supported on the root directory only.
+ *
+ * @param label the new volume label
+ * @throws IllegalArgumentException if the label is too long
+ * @throws UnsupportedOperationException if this is not a root directory
+ * @see #isRoot()
+ */
+ public void setLabel(String label) throws IllegalArgumentException,
+ UnsupportedOperationException, IOException {
+
+ checkRoot();
+
+ if (label.length() > MAX_LABEL_LENGTH) throw new
+ IllegalArgumentException("label too long");
+
+ if (this.volumeLabel != null) {
+ if (label == null) {
+ changeSize(getSize() - 1);
+ this.volumeLabel = null;
+ } else {
+ ShortName.checkValidChars(label.toCharArray());
+ this.volumeLabel = label;
+ }
+ } else {
+ if (label != null) {
+ changeSize(getSize() + 1);
+ ShortName.checkValidChars(label.toCharArray());
+ this.volumeLabel = label;
+ }
+ }
+
+ this.dirty = true;
+ }
+
+}