Home | History | Annotate | Download | only in fat
      1 /*
      2  * Copyright (C) 2003-2009 JNode.org
      3  *               2009,2010 Matthias Treydte <mt (at) waldheinz.de>
      4  *
      5  * This library is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU Lesser General Public License as published
      7  * by the Free Software Foundation; either version 2.1 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This library is distributed in the hope that it will be useful, but
     11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
     13  * License for more details.
     14  *
     15  * You should have received a copy of the GNU Lesser General Public License
     16  * along with this library; If not, write to the Free Software Foundation, Inc.,
     17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     18  */
     19 
     20 package de.waldheinz.fs.fat;
     21 
     22 import java.io.IOException;
     23 import java.nio.ByteBuffer;
     24 import java.util.ArrayList;
     25 import java.util.Arrays;
     26 import java.util.List;
     27 
     28 /**
     29  * This is the abstract base class for all directory implementations.
     30  *
     31  * @author Ewout Prangsma &lt;epr at jnode.org&gt;
     32  * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
     33  */
     34 abstract class AbstractDirectory {
     35 
     36     /**
     37      * The maximum length of the volume label.
     38      *
     39      * @see #setLabel(java.lang.String)
     40      */
     41     public static final int MAX_LABEL_LENGTH = 11;
     42 
     43     private final List<FatDirectoryEntry> entries;
     44     private final boolean readOnly;
     45     private final boolean isRoot;
     46 
     47     private boolean dirty;
     48     private int capacity;
     49     private String volumeLabel;
     50 
     51     /**
     52      * Creates a new instance of {@code AbstractDirectory}.
     53      *
     54      * @param capacity the initial capacity of the new instance
     55      * @param readOnly if the instance should be read-only
     56      * @param isRoot if the new {@code AbstractDirectory} represents a root
     57      *      directory
     58      */
     59     protected AbstractDirectory(
     60             int capacity, boolean readOnly, boolean isRoot) {
     61 
     62         this.entries = new ArrayList<FatDirectoryEntry>();
     63         this.capacity = capacity;
     64         this.readOnly = readOnly;
     65         this.isRoot = isRoot;
     66     }
     67 
     68     /**
     69      * Gets called when the {@code AbstractDirectory} must read it's content
     70      * off the backing storage. This method must always fill the buffer's
     71      * remaining space with the bytes making up this directory, beginning with
     72      * the first byte.
     73      *
     74      * @param data the {@code ByteBuffer} to fill
     75      * @throws IOException on read error
     76      */
     77     protected abstract void read(ByteBuffer data) throws IOException;
     78 
     79     /**
     80      * Gets called when the {@code AbstractDirectory} wants to write it's
     81      * contents to the backing storage. This method is expected to write the
     82      * buffer's remaining data to the storage, beginning with the first byte.
     83      *
     84      * @param data the {@code ByteBuffer} to write
     85      * @throws IOException on write error
     86      */
     87     protected abstract void write(ByteBuffer data) throws IOException;
     88 
     89     /**
     90      * Returns the number of the cluster where this directory is stored. This
     91      * is important when creating the ".." entry in a sub-directory, as this
     92      * entry must poing to the storage cluster of it's parent.
     93      *
     94      * @return this directory's storage cluster
     95      */
     96     protected abstract long getStorageCluster();
     97 
     98     /**
     99      * Gets called by the {@code AbstractDirectory} when it has determined that
    100      * it should resize because the number of entries has changed.
    101      *
    102      * @param entryCount the new number of entries this directory needs to store
    103      * @throws IOException on write error
    104      * @throws DirectoryFullException if the FAT12/16 root directory is full
    105      * @see #sizeChanged(long)
    106      * @see #checkEntryCount(int)
    107      */
    108     protected abstract void changeSize(int entryCount)
    109             throws DirectoryFullException, IOException;
    110 
    111     /**
    112      * Replaces all entries in this directory.
    113      *
    114      * @param newEntries the new directory entries
    115      */
    116     public void setEntries(List<FatDirectoryEntry> newEntries) {
    117         if (newEntries.size() > capacity)
    118             throw new IllegalArgumentException("too many entries");
    119 
    120         this.entries.clear();
    121         this.entries.addAll(newEntries);
    122     }
    123 
    124     /**
    125      *
    126      *
    127      * @param newSize the new storage space for the directory in bytes
    128      * @see #changeSize(int)
    129      */
    130     protected final void sizeChanged(long newSize) throws IOException {
    131         final long newCount = newSize / FatDirectoryEntry.SIZE;
    132         if (newCount > Integer.MAX_VALUE)
    133             throw new IOException("directory too large");
    134 
    135         this.capacity = (int) newCount;
    136     }
    137 
    138     public final FatDirectoryEntry getEntry(int idx) {
    139         return this.entries.get(idx);
    140     }
    141 
    142     /**
    143      * Returns the current capacity of this {@code AbstractDirectory}.
    144      *
    145      * @return the number of entries this directory can hold in its current
    146      *      storage space
    147      * @see #changeSize(int)
    148      */
    149     public final int getCapacity() {
    150         return this.capacity;
    151     }
    152 
    153     /**
    154      * The number of entries that are currently stored in this
    155      * {@code AbstractDirectory}.
    156      *
    157      * @return the current number of directory entries
    158      */
    159     public final int getEntryCount() {
    160         return this.entries.size();
    161     }
    162 
    163     public boolean isReadOnly() {
    164         return readOnly;
    165     }
    166 
    167     public final boolean isRoot() {
    168         return this.isRoot;
    169     }
    170 
    171     /**
    172      * Gets the number of directory entries in this directory. This is the
    173      * number of "real" entries in this directory, possibly plus one if a
    174      * volume label is set.
    175      *
    176      * @return the number of entries in this directory
    177      */
    178     public int getSize() {
    179         return entries.size() + ((this.volumeLabel != null) ? 1 : 0);
    180     }
    181 
    182     /**
    183      * Mark this directory as dirty.
    184      */
    185     protected final void setDirty() {
    186         this.dirty = true;
    187     }
    188 
    189     /**
    190      * Checks if this {@code AbstractDirectory} is a root directory.
    191      *
    192      * @throws UnsupportedOperationException if this is not a root directory
    193      * @see #isRoot()
    194      */
    195     private void checkRoot() throws UnsupportedOperationException {
    196         if (!isRoot()) {
    197             throw new UnsupportedOperationException(
    198                     "only supported on root directories");
    199         }
    200     }
    201 
    202     /**
    203      * Mark this directory as not dirty.
    204      */
    205     private void resetDirty() {
    206         this.dirty = false;
    207     }
    208 
    209     /**
    210      * Flush the contents of this directory to the persistent storage
    211      */
    212     public void flush() throws IOException {
    213 
    214         final ByteBuffer data = ByteBuffer.allocate(
    215                 getCapacity() * FatDirectoryEntry.SIZE);
    216 
    217         for (int i=0; i < entries.size(); i++) {
    218             final FatDirectoryEntry entry = entries.get(i);
    219 
    220             if (entry != null) {
    221                 entry.write(data);
    222             }
    223         }
    224 
    225         /* TODO: the label could be placed directly the dot entries */
    226 
    227         if (this.volumeLabel != null) {
    228             final FatDirectoryEntry labelEntry =
    229                     FatDirectoryEntry.createVolumeLabel(volumeLabel);
    230 
    231             labelEntry.write(data);
    232         }
    233 
    234         if (data.hasRemaining()) {
    235             FatDirectoryEntry.writeNullEntry(data);
    236         }
    237 
    238         data.flip();
    239 
    240         write(data);
    241         resetDirty();
    242     }
    243 
    244     protected final void read() throws IOException {
    245         final ByteBuffer data = ByteBuffer.allocate(
    246                 getCapacity() * FatDirectoryEntry.SIZE);
    247 
    248         read(data);
    249         data.flip();
    250 
    251         for (int i=0; i < getCapacity(); i++) {
    252             final FatDirectoryEntry e =
    253                     FatDirectoryEntry.read(data, isReadOnly());
    254 
    255             if (e == null) break;
    256 
    257             if (e.isVolumeLabel()) {
    258                 if (!this.isRoot) throw new IOException(
    259                         "volume label in non-root directory");
    260 
    261                 this.volumeLabel = e.getVolumeLabel();
    262             } else {
    263                 entries.add(e);
    264             }
    265         }
    266     }
    267 
    268     public void addEntry(FatDirectoryEntry e) throws IOException {
    269         assert (e != null);
    270 
    271         if (getSize() == getCapacity()) {
    272             changeSize(getCapacity() + 1);
    273         }
    274 
    275         entries.add(e);
    276     }
    277 
    278     public void addEntries(FatDirectoryEntry[] entries)
    279             throws IOException {
    280 
    281         if (getSize() + entries.length > getCapacity()) {
    282             changeSize(getSize() + entries.length);
    283         }
    284 
    285         this.entries.addAll(Arrays.asList(entries));
    286     }
    287 
    288     public void removeEntry(FatDirectoryEntry entry) throws IOException {
    289         assert (entry != null);
    290 
    291         this.entries.remove(entry);
    292         changeSize(getSize());
    293     }
    294 
    295     /**
    296      * Returns the volume label that is stored in this directory. Reading the
    297      * volume label is only supported for the root directory.
    298      *
    299      * @return the volume label stored in this directory, or {@code null}
    300      * @throws UnsupportedOperationException if this is not a root directory
    301      * @see #isRoot()
    302      */
    303     public String getLabel() throws UnsupportedOperationException {
    304         checkRoot();
    305 
    306         return volumeLabel;
    307     }
    308 
    309     public FatDirectoryEntry createSub(Fat fat) throws IOException {
    310         final ClusterChain chain = new ClusterChain(fat, false);
    311         chain.setChainLength(1);
    312 
    313         final FatDirectoryEntry entry = FatDirectoryEntry.create(true);
    314         entry.setStartCluster(chain.getStartCluster());
    315 
    316         final ClusterChainDirectory dir =
    317                 new ClusterChainDirectory(chain, false);
    318 
    319         /* add "." entry */
    320 
    321         final FatDirectoryEntry dot = FatDirectoryEntry.create(true);
    322         dot.setShortName(ShortName.DOT);
    323         dot.setStartCluster(dir.getStorageCluster());
    324         copyDateTimeFields(entry, dot);
    325         dir.addEntry(dot);
    326 
    327         /* add ".." entry */
    328 
    329         final FatDirectoryEntry dotDot = FatDirectoryEntry.create(true);
    330         dotDot.setShortName(ShortName.DOT_DOT);
    331         dotDot.setStartCluster(getStorageCluster());
    332         copyDateTimeFields(entry, dotDot);
    333         dir.addEntry(dotDot);
    334 
    335         dir.flush();
    336 
    337         return entry;
    338     }
    339 
    340     private static void copyDateTimeFields(
    341             FatDirectoryEntry src, FatDirectoryEntry dst) {
    342 
    343         dst.setCreated(src.getCreated());
    344         dst.setLastAccessed(src.getLastAccessed());
    345         dst.setLastModified(src.getLastModified());
    346     }
    347 
    348     /**
    349      * Sets the volume label that is stored in this directory. Setting the
    350      * volume label is supported on the root directory only.
    351      *
    352      * @param label the new volume label
    353      * @throws IllegalArgumentException if the label is too long
    354      * @throws UnsupportedOperationException if this is not a root directory
    355      * @see #isRoot()
    356      */
    357     public void setLabel(String label) throws IllegalArgumentException,
    358             UnsupportedOperationException, IOException {
    359 
    360         checkRoot();
    361 
    362         if (label.length() > MAX_LABEL_LENGTH) throw new
    363                 IllegalArgumentException("label too long");
    364 
    365         if (this.volumeLabel != null) {
    366             if (label == null) {
    367                 changeSize(getSize() - 1);
    368                 this.volumeLabel = null;
    369             } else {
    370                 ShortName.checkValidChars(label.toCharArray());
    371                 this.volumeLabel = label;
    372             }
    373         } else {
    374             if (label != null) {
    375                 changeSize(getSize() + 1);
    376                 ShortName.checkValidChars(label.toCharArray());
    377                 this.volumeLabel = label;
    378             }
    379         }
    380 
    381         this.dirty = true;
    382     }
    383 
    384 }
    385