/* XXL: The eXtensible and fleXible Library for data processing
Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger
Head of the Database Research Group
Department of Mathematics and Computer Science
University of Marburg
Germany
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 3 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, see <http://www.gnu.org/licenses/>.
http://code.google.com/p/xxl/
*/
package xxl.core.io.fat;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import xxl.core.io.fat.errors.DirectoryException;
import xxl.core.io.fat.errors.FileDoesntExist;
import xxl.core.io.fat.errors.IllegalName;
import xxl.core.io.fat.errors.NameAllreadyExists;
import xxl.core.io.fat.errors.NotEnoughMemory;
import xxl.core.io.fat.util.ByteArrayConversionsLittleEndian;
import xxl.core.io.fat.util.MyMath;
import xxl.core.io.fat.util.StringOperations;
import xxl.core.util.Arrays;
/**
* This class represents a directory for the FAT. One directory entry consist
* of 32 bytes. The order of numbers is little endian.
*/
public class DIR
{
/**
* Indicates that the entry is marked as free.
*/
public static final byte FREE_ENTRY = (byte)0xE5;
/**
* Indicates that the entry is marked as free and all next entries are free, too.
*/
public static final byte FREE_ENTRY_AND_NEXTS_FREE = (byte)0x00;
/**
* Indicates that the entry is marked as free.
*/
public static final byte FREE_ENTRY_KANJI = (byte)0x05;
/**
* Indicates that file is read only.
*/
public static final byte ATTR_READ_ONLY = (byte)0x01;
/**
* Indicates that normal directory listings should not show this file.
*/
public static final byte ATTR_HIDDEN = (byte)0x02;
/**
* Indicates that this is an operating system file.
*/
public static final byte ATTR_SYSTEM = (byte)0x04;
/**
* There should only be one "file" on the volume that has this
* attribute set, and that file must be in the root directory.
* This name of this file is actually the label for the volume.
* firstClusterHI and firstClusterLO must always be 0 for the
* volume label (no data clusters are allocated to the volume label
* file).
*/
public static final byte ATTR_VOLUME_ID = (byte)0x08;
/**
* Indicates that this file is actually a container for other files.
*/
public static final byte ATTR_DIRECTORY = (byte)0x10;
/**
* This attribute supports backup utilities. This bit is set by the FAT
* file system driver when a file is created, renamed, or written to.
* Backup utilities may use this attribute to indicate which files on
* the volume have been modified since the last time that a backup
* was performed.
*/
public static final byte ATTR_ARCHIVE = (byte)0x20;
/**
* Indicates that the "file" is actually part of the long name entry
* for some other file.
*/
public static final byte ATTR_LONG_NAME = (byte)(ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID);
/**
* A mask for determining whether an entry is a long-name
* sub-component.
*/
public static final byte ATTR_LONG_NAME_MASK = (byte)(ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID | ATTR_DIRECTORY | ATTR_ARCHIVE);
/**
* Indicates that the "entry" is the last part of the long name entry
* set.
*/
public static final byte LAST_LONG_ENTRY = (byte)0x40;
/**
* Indicates that only the root entry should be listed, all other directory entries will be filtered.
*/
public static final int LIST_ROOT_ENTRY = 1;
/**
* Indicates that only the dot and dot dot entries should be listed,
* all other directory entries will be filtered.
*/
public static final int LIST_DOT_AND_DOTDOT_ENTRY = 2;
/**
* Indicates that only directory entries (entries with the set ATTR_DIRECTORY flag) should
* be listed, all other entries will be filtered.
*/
public static final int LIST_DIRECTORY_ENTRY = 4;
/**
* Indicates that only file entries should be listed, all other directory entries will be filtered.
*/
public static final int LIST_FILE_ENTRY = 8;
/**
* Indicates that only the free entries should be listed, all other directory entries will be filtered.
*/
public static final int LIST_FREE_ENTRY = 16;
/**
* Indicates that only the entries which have an entry in the FAT12 will be listed,
* all other directory entries will be filtered.
*/
public static final int LIST_FAT12_ENTRY = LIST_DIRECTORY_ENTRY | LIST_FILE_ENTRY;
/**
* Indicates that only the entries which have an entry in the FAT16 will be listed,
* all other directory entries will be filtered.
*/
public static final int LIST_FAT16_ENTRY = LIST_DIRECTORY_ENTRY | LIST_FILE_ENTRY;
/**
* Indicates that only the entries which have an entry in the FAT32 will be listed,
* all other directory entries will be filtered.
*/
public static final int LIST_FAT32_ENTRY = LIST_DIRECTORY_ENTRY | LIST_FILE_ENTRY | LIST_ROOT_ENTRY;
/**
* Indicates that all entries will be listed except the free directory entries.
*/
public static final int LIST_ALL_EXCEPT_FREE = LIST_ROOT_ENTRY | LIST_DOT_AND_DOTDOT_ENTRY | LIST_DIRECTORY_ENTRY | LIST_FILE_ENTRY;
/**
* Indicates that every entry should be listed. No entry is filtered.
*/
public static final int LIST_EVERYTHING_ENTRY = LIST_ROOT_ENTRY | LIST_DOT_AND_DOTDOT_ENTRY | LIST_DIRECTORY_ENTRY | LIST_FILE_ENTRY | LIST_FREE_ENTRY;
/**
* Object of the FATDevice;
*/
private FATDevice device = null;
/**
* Object of the BPB.
*/
private BPB bpb = null;
/**
* Contains the first sector number of the root directory.
*/
private long firstRootDirSecNum;
/**
* Contains the number of root directory sectors.
*/
private long numRootDirSectors;
/**
* Byte array to store one sector.
*/
private byte[] sectorBuffer;
/**
* Byte array to store one cluster.
*/
private byte[] clusterBuffer;
/**
* Root directory as byte array.
*/
private byte[] rootDirectory;
//private LinkedList directory;
/**
* Calendar to get actual time and date.
*/
private GregorianCalendar calendar = new GregorianCalendar();
/**
* The name of a directory entry has some restrictions. This set contains
* characters that are not allowed to be in a short name.
*/
public static final HashSet illicitValues = new HashSet(30, 0.8f);
//initialize the hashtable with values that are illicit
static
{
illicitValues.add(new Short((short)0x22));
illicitValues.add(new Short((short)0x2A));
illicitValues.add(new Short((short)0x2B));
illicitValues.add(new Short((short)0x2C));
illicitValues.add(new Short((short)0x2E));
illicitValues.add(new Short((short)0x2F));
illicitValues.add(new Short((short)0x3A));
illicitValues.add(new Short((short)0x3B));
illicitValues.add(new Short((short)0x3C));
illicitValues.add(new Short((short)0x3D));
illicitValues.add(new Short((short)0x3E));
illicitValues.add(new Short((short)0x3F));
illicitValues.add(new Short((short)0x5B));
illicitValues.add(new Short((short)0x5C));
illicitValues.add(new Short((short)0x5D));
illicitValues.add(new Short((short)0x7C));
}
/**
* This class contains the information about one or more free directory entries.
*/
protected class FreeInformation
{
/**
* Index to the start position of the ordered free directory entry.
*/
int freeEntryIndex;
/**
* Contains the ordered list of clusters where the ordered free directory entry can be stored.
* The first cluster number in the list is the one for the free entry index.
* The stored cluster numbers are all direct. See documentation for the meaning
* of direct and indirect addressing.
*/
List freeClusters;
/**
* Is the first cluster of the free cluster list, all other clusters must be loaded.
*/
byte[] cluster;
/**
* Indicates if the information of this class belongs to the root directory.
* Notice that the cluster numbers stored at the freeClusters list are always
* direct, so you don't have to transform with device.getFirstSectorNumberOfCluster(...).
*/
boolean isRoot;
/**
* Creates an instance of this object.
* @param freeEntryIndex the index of the free entry.
* @param freeClusters a list of free clusters, this clusters must be direct accessible.
* @param cluster byte array representation of a cluster.
* @param isRoot indicates if this free information belongs to the root directory.
*/
public FreeInformation(int freeEntryIndex, List freeClusters, byte[] cluster, boolean isRoot)
{
this.freeEntryIndex = freeEntryIndex;
this.freeClusters = freeClusters;
this.cluster = cluster;
this.isRoot = isRoot;
}
/**
* Return a String with the information about the variables of this class.
* @return String with the information.
*/
public String toString()
{
String res = "FreeInformation:\n";
res += "freeEntryIndex "+freeEntryIndex;
res += ", list length "+freeClusters.size()+", ";
res += freeClusters +"\n";
res += "isRoot "+isRoot+"\n";
return res;
}
} //end inner class FreeInformation
/**
* This class contains information about a number of a cluster.
* The old song: If the clusterNumber belongs to the root directory
* in case of FAT12 or FAT16 the clusterNumber is direct and isRoot
* has to be true. Otherwise the clusterNumber is indirect.
*/
protected class ClusterTuple
{
/**
* Contains a number of a cluster.
*/
public long clusterNumber;
/**
* Indicates if the clusterNumber belongs to the root directory of FAT12 or FAT16.
* For a FAT32 file system this flag must be false also for the root directory.
*/
public boolean isRoot;
/**
* Creates an instance of this object.
* @param clusterNumber the number of a cluster.
* @param isRoot indicates if the given clusterNumber belongs to the root directory.
*/
public ClusterTuple(long clusterNumber, boolean isRoot)
{
this.clusterNumber = clusterNumber;
this.isRoot = isRoot;
} //end constructor
/**
* Print the information about the variables of this class.
* @return information about the variables of this class.
*/
public String toString()
{
String res = "ClusterTuple:\n";
res += "cn "+clusterNumber+" isRoot "+isRoot+"\n";
return res;
} //end toString()
} //end inner class ClusterTuple
/**
* Inner class represents one entry of the directory. One entry consists
* of 32 bytes. Since the naming convention is no longer restricted to
* 8.3 one entry may be consist of x*32 bytes. Where x is a positive non
* zero integer.
*/
protected class DirectoryStructure
{
/**
* Short name representation of the directory name. The format is
* 8.3.
*/
protected String shortName;
/**
* Long name representation of the directory name, in case the given
* directory name is not storable as shortName.
*/
protected String longName;
/**
* Attributes of the directory entry.
*/
protected short attributes;
/**
* Reserved for use by Windows NT. Value is set to 0 when a file is created.
*/
private short ntReserved;
/**
* Millisecond stamp at file creation time. This field actually contains a count
* of tenths of a second. The granularity of the seconds part of creationTime is
* 2 seconds so this field is a count of tenths of a second and its value range is
* 0-199 inclusive.
*/
protected short creationTimeTenth;
/**
* The time stamp the file was created. A time stamp is a 16-bit field
* that has a granularity of 2 seconds. Bit 0 is the LSB of the 16-bit word,
* bit 15 is the MSB of the 16-bit word.
* Bits 0-4: 2-second count, valid range 0-29 inclusive (0-58 seconds).
* Bits 5-10: Minutes, valid value range 0-59 inclusive.
* Bits 11-15: Hours, valid range 0-23 inclusive.
*/
protected int creationTime;
/**
* The date stamp the file was created. A date stamp is a 16-bit field
* that that is basically a date relative to the MS-DOS epoch of 01/01/1980.
* Bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the 16-bit word:
* Bits 0-4: Day, valid range 1-31 inclusive.
* Bits 5-8: Month, 1=January, valid range 1-12 inclusive.
* Bits 9-15: Count of years from 1980, valid range 0-127 inclusive (1980-2107).
*/
protected int creationDate;
/**
* The date stamp the file was last accessed. A date stamp is a 16-bit field
* that that is basically a date relative to the MS-DOS epoch of 01/01/1980.
* Bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the 16-bit word:
* Bits 0-4: Day, valid range 1-31 inclusive.
* Bits 5-8: Month, 1=January, valid range 1-12 inclusive.
* Bits 9-15: Count of years from 1980, valid range 0-127 inclusive (1980-2107).
*/
protected int lastAccessDate;
/**
* The high part of the cluster number stored at the directory entry.
*/
protected int firstClusterHI;
/**
* The time stamp the file was last written. A time stamp is a 16-bit field
* that has a granularity of 2 seconds. Bit 0 is the LSB of the 16-bit word,
* bit 15 is the MSB of the 16-bit word.
* Bits 0-4: 2-second count, valid range 0-29 inclusive (0-58 seconds).
* Bits 5-10: Minutes, valid value range 0-59 inclusive.
* Bits 11-15: Hours, valid range 0-23 inclusive.
*/
protected int writeTime;
/**
* The date stamp the file was last written. A date stamp is a 16-bit field
* that that is basically a date relative to the MS-DOS epoch of 01/01/1980.
* Bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the 16-bit word:
* Bits 0-4: Day, valid range 1-31 inclusive.
* Bits 5-8: Month, 1=January, valid range 1-12 inclusive.
* Bits 9-15: Count of years from 1980, valid range 0-127 inclusive (1980-2107).
*/
protected int writeDate;
/**
* The low part of the cluster number stored at the directory entry.
*/
protected int firstClusterLO;
/**
* The length of the file stored at the directory entry.
*/
protected long fileSize;
/**
* The number of the cluster where the directory entry should be stored.
*/
protected long clusterNumber; //this is the cluster number where the directory entry should be stored
/**
* Indicates if the clusterNumber belongs to the root directory.
*/
protected boolean isRoot;
/**
* Contains the number of entries that this directory entry uses.
*/
protected int numOfEntries = 0;
/**
* Creates an instance of this object.
* @param name the name stored at the directory entry.
* @param clusterNumber the number of the cluster where the directory should be stored.
* @param isRoot indicates if the given clusterNumber belongs to the root directory.
* @throws IllegalName in case the given name is not legal.
*/
public DirectoryStructure(String name, long clusterNumber, boolean isRoot) throws IllegalName
{
this(name, 0, DIR.ATTR_ARCHIVE, 0, clusterNumber, isRoot);
} //end constructor
/**
* Creates an instance of this object.
* @param name the name stored at the directory entry.
* @param attribute the attribute stored at the directory entry.
* @param clusterNumber the number of the cluster where the directory should be stored.
* @param isRoot indicates if the given clusterNumber belongs to the root directory.
* @throws IllegalName in case the given name is not legal.
*/
public DirectoryStructure( String name,
byte attribute,
long clusterNumber,
boolean isRoot) throws IllegalName
{
this(name, 0, attribute, 0, clusterNumber, isRoot);
} //end constructor
/**
* Creates an instance of this object.
* @param name the name stored at the directory entry.
* @param entryClusterNumber the number of the cluster stored at the directory entry.
* @param clusterNumber the number of the cluster where the directory should be stored.
* @param isRoot indicates if the given clusterNumber belongs to the root directory.
* @throws IllegalName in case the given name is not legal.
*/
public DirectoryStructure( String name,
long entryClusterNumber,
long clusterNumber,
boolean isRoot) throws IllegalName
{
this(name, entryClusterNumber, DIR.ATTR_ARCHIVE, 0, clusterNumber, isRoot);
} //end constructor
/**
* Creates an instance of this object.
* @param name the name stored at the directory entry.
* @param entryClusterNumber the number of the cluster stored at the directory entry.
* @param attribute the attribute stored at the directory entry.
* @param fileSize the size of the file stored at the directory entry.
* @param clusterNumber the number of the cluster where the directory should be stored.
* @param isRoot indicates if the given clusterNumber belongs to the root directory.
* @throws IllegalName in case the given name is not legal.
*/
public DirectoryStructure( String name,
long entryClusterNumber,
byte attribute,
long fileSize,
long clusterNumber,
boolean isRoot) throws IllegalName
{
this( name,
attribute,
calendar.get(Calendar.HOUR_OF_DAY), //crtTimeHour
calendar.get(Calendar.MINUTE), //crtTimeMinute
calendar.get(Calendar.SECOND), //crtTimeSecond
calendar.get(Calendar.MILLISECOND), //crtTimeMillisecond
calendar.get(Calendar.DAY_OF_MONTH), //crtDateDayOfMonth
calendar.get(Calendar.MONTH) + 1, //crtDateMonth, the month count starts at 0
calendar.get(Calendar.YEAR), //crtDateYear,
entryClusterNumber,
fileSize,
clusterNumber,
isRoot);
} //end constructor
/**
* Creates an instance of this object.
* @param name the name stored at the directory entry.
* @param attribute the attribute stored at the directory entry.
* @param crtTimeHour creation time hour valid range form 1 to 24.
* @param crtTimeMinute creation time minute valid range from 0 to 59.
* @param crtTimeSecond creation time second valid range from 0 to 59.
* @param crtTimeMillisecond creation time millisecond valid range from 0 to 999.
* @param crtDateDayOfMonth creation day valid range from 1 to 31.
* @param crtDateMonth creation month valid range from 1 to 12.
* @param crtDateYear creation year.
* @param entryClusterNumber the number of the cluster stored at the directory entry.
* @param fileSize the size of the file stored at the directory entry.
* @param clusterNumber the number of the cluster where the directory should be stored.
* @param isRoot indicates if the given clusterNumber belongs to the root directory.
* @throws IllegalName in case the given name is not legal.
*/
public DirectoryStructure( String name,
byte attribute,
int crtTimeHour,
int crtTimeMinute,
int crtTimeSecond,
int crtTimeMillisecond,
int crtDateDayOfMonth,
int crtDateMonth,
int crtDateYear,
long entryClusterNumber,
long fileSize,
long clusterNumber,
boolean isRoot) throws IllegalName
{
this.clusterNumber = clusterNumber;
this.isRoot = isRoot;
setName(name);
setAttribute(attribute);
ntReserved = 0;
setCreationTime(crtTimeHour - 1, crtTimeMinute, (crtTimeSecond >> 1), ((crtTimeMillisecond / 10) << 1));
setCreationDate(crtDateDayOfMonth, crtDateMonth, crtDateYear-1980);
setLastAccessDate(crtDateDayOfMonth, crtDateMonth, crtDateYear-1980);
setLastWriteTime(crtTimeHour - 1, crtTimeMinute, (crtTimeSecond >> 1));
setLastWriteDate(crtDateDayOfMonth, crtDateMonth, crtDateYear-1980);
firstClusterHI = (int)((entryClusterNumber >> 16) & 0x000000000000FFFF);
firstClusterLO = (int)(entryClusterNumber & 0x000000000000FFFF);
this.fileSize = fileSize;
} //end constructor
/**
* Initialize this directory entry with the given byte array.
* All variables will be initialized.
* @param dirStruc a directory entry as byte array, the length has to be a
* multiple of 32 byte.
* @param clusterNumber is the number of the cluster where the directory entry is taken from.
* It is needed because of the numeric tail of the short name.
* @param isRoot indicates if the given clusterNumber belongs to the root directory.
*/
public DirectoryStructure(byte[] dirStruc, long clusterNumber, boolean isRoot)
{
this.clusterNumber = clusterNumber;
this.isRoot = isRoot;
//extract the short name together with the numeric tail algorithm
String str = ByteArrayConversionsLittleEndian.byteToString(dirStruc, dirStruc.length - 32 + 0, dirStruc.length - 32 + 11);
shortName = numericTailGeneration(str, new ClusterChainIterator(device, clusterNumber, isRoot));
longName = getLongName(dirStruc);
attributes = ByteArrayConversionsLittleEndian.convShort(dirStruc[dirStruc.length - 32 + 11]);
ntReserved = ByteArrayConversionsLittleEndian.convShort(dirStruc[dirStruc.length - 32 + 12]);
creationTimeTenth = ByteArrayConversionsLittleEndian.convShort(dirStruc[dirStruc.length - 32 + 13]);
creationTime = ByteArrayConversionsLittleEndian.convInt(dirStruc[dirStruc.length - 32 + 14], dirStruc[dirStruc.length - 32 + 15]);
creationDate = ByteArrayConversionsLittleEndian.convInt(dirStruc[dirStruc.length - 32 + 16], dirStruc[dirStruc.length - 32 + 17]);
lastAccessDate = ByteArrayConversionsLittleEndian.convInt(dirStruc[dirStruc.length - 32 + 18], dirStruc[dirStruc.length - 32 + 19]);
firstClusterHI = ByteArrayConversionsLittleEndian.convInt(dirStruc[dirStruc.length - 32 + 20], dirStruc[dirStruc.length - 32 + 21]);
writeTime = ByteArrayConversionsLittleEndian.convInt(dirStruc[dirStruc.length - 32 + 22], dirStruc[dirStruc.length - 32 + 23]);
writeDate = ByteArrayConversionsLittleEndian.convInt(dirStruc[dirStruc.length - 32 + 24], dirStruc[dirStruc.length - 32 + 25]);
firstClusterLO = ByteArrayConversionsLittleEndian.convInt(dirStruc[dirStruc.length - 32 + 26], dirStruc[dirStruc.length - 32 + 27]);
fileSize = ByteArrayConversionsLittleEndian.convLong(dirStruc[dirStruc.length - 32 + 28], dirStruc[dirStruc.length - 32 + 29], dirStruc[dirStruc.length - 32 + 30], dirStruc[dirStruc.length - 32 + 31]);
} //end constructor
/**
* Set the name of a file or directory. There are some restrictions
* on the name.
* @param str the name, it can be either a short name with 8.3 naming convention or a
* long name.
* @throws IllegalName in case the name is not legal.
*/
public void setName(String str) throws IllegalName
{
if (str.equals(System.getProperty("file.separator")))
{
shortName = str+" ";
numOfEntries = 1;
return;
}
if (str.equals(". ") || str.equals(".. "))
{
shortName = str;
numOfEntries = 1;
return;
}
shortName = numericTailGeneration(str, new ClusterChainIterator(device, clusterNumber, isRoot));
int dotIndex = shortName.lastIndexOf(".");
String shortFileName = "";
String shortFileExtension = "";
if (dotIndex >= 0)
{
shortFileName = shortName.substring(0, dotIndex);
if (DIR.containsIllicitValues(shortFileName.getBytes()))
throw new IllegalName("");
shortFileExtension = shortName.substring(dotIndex + 1, shortName.length());
if (DIR.containsIllicitValues(shortFileExtension.getBytes()))
throw new IllegalName("");
}
else
shortFileName = shortName;
for (int i=shortFileName.length(); i < 8; i++)
shortFileName += " ";
for (int i=shortFileExtension.length(); i < 3; i++)
shortFileExtension += " ";
numOfEntries = 1;
if (!isShortName(str) || !str.toUpperCase().equals(str))
{
longName = str;
numOfEntries += MyMath.roundUp(str.length() / 13.0);
}
shortName = shortFileName + shortFileExtension;
//all short name entries must be in upper case
shortName = shortName.toUpperCase();
} //end setName
/**
* Set the cluster number of this entry to the given clusterNumber.
* @param clusterNumber number of cluster to set.
*/
public void setClusterNumber(long clusterNumber)
{
firstClusterHI = (int)((clusterNumber >> 16) & 0x000000000000FFFF);
firstClusterLO = (int)(clusterNumber & 0x000000000000FFFF);
} //end setClusterNumber(long clusterNumber)
/**
* Set the attribute. Only the first 6 bits are free to change. The
* upper 2 bit (of the byte type) are reserved.
* @param attribute the attribute for the directory entry.
*/
public void setAttribute(short attribute)
{
//upper two bits are reserved
attributes &= 0x00C0; //clear the first 6 bits
attribute &= 0x003F; //only the first 6 bits should have a value
attributes |= attribute; //set the first 6 bits
} //end setAttribute
/**
* Set the creationTime to the actual time. A time stamp is a 16-bit field
* that has a granularity of 2 seconds. Bit 0 is the LSB of the 16-bit word,
* bit 15 is the MSB of the 16-bit word.
* Bits 0-4: 2-second count, valid range 0-29 inclusive (0-58 seconds).
* Bits 5-10: Minutes, valid value range 0-59 inclusive.
* Bits 11-15: Hours, valid range 0-23 inclusive.
*/
public void setCreationTime()
{
setCreationTime(
calendar.get(Calendar.HOUR_OF_DAY) - 1,
calendar.get(Calendar.MINUTE),
(calendar.get(Calendar.SECOND) >> 1),
(calendar.get(Calendar.MILLISECOND) / 10) << 1
);
} //end setCreationTime()
/**
* Set the creationTime to the given parameters. A time stamp is a 16-bit field
* that has a granularity of 2 seconds. Bit 0 is the LSB of the 16-bit word,
* bit 15 is the MSB of the 16-bit word.
* Bits 0-4: 2-second count, valid range 0-29 inclusive (0-58 seconds).
* Bits 5-10: Minutes, valid value range 0-59 inclusive.
* Bits 11-15: Hours, valid range 0-23 inclusive.
* All parameter must be in the valid range, no conversion will be done.
* @param crtTimeHour creation time hour.
* @param crtTimeMinute creation time minute.
* @param crtTimeSecond creation time second.
* @param crtTimeMillisecond creation time millisecond
*/
public void setCreationTime(int crtTimeHour,
int crtTimeMinute,
int crtTimeSecond,
int crtTimeMillisecond)
{
creationTime = 0;
creationTime = crtTimeSecond >> 1;
creationTime |= (crtTimeMinute << 5);
creationTime |= (crtTimeHour << 11);
creationTimeTenth = (short)crtTimeMillisecond;
} //end setCreationTime(...)
/**
* Set the creationDate to the actual date. A date stamp is a 16-bit field
* that that is basically a date relative to the MS-DOS epoch of 01/01/1980.
* Bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the 16-bit word:
* Bits 0-4: Day, valid range 1-31 inclusive.
* Bits 5-8: Month, 1=January, valid range 1-12 inclusive.
* Bits 9-15: Count of years from 1980, valid range 0-127 inclusive (1980-2107).
*/
public void setCreationDate()
{
setCreationDate(
calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.MONTH) + 1,
calendar.get(Calendar.YEAR) - 1980
);
} //end setCreationDate()
/**
* Set the creationDate to the actual date. A date stamp is a 16-bit field
* that that is basically a date relative to the MS-DOS epoch of 01/01/1980.
* Bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the 16-bit word:
* Bits 0-4: Day, valid range 1-31 inclusive.
* Bits 5-8: Month, 1=January, valid range 1-12 inclusive.
* Bits 9-15: Count of years from 1980, valid range 0-127 inclusive (1980-2107).
* All parameter must be in the valid range, no conversion will be done.
* @param crtDateDayOfMonth creation day.
* @param crtDateMonth creation month.
* @param countOfYears the count of years since 1980.
*/
public void setCreationDate(int crtDateDayOfMonth, int crtDateMonth, int countOfYears)
{
creationDate = 0;
creationDate = (crtDateDayOfMonth);
creationDate |= (crtDateMonth << 5);
creationDate |= (countOfYears << 9);
} //end setCreationDate(...)
/**
* Set the last access to the actual date. A date stamp is a 16-bit field
* that that is basically a date relative to the MS-DOS epoch of 01/01/1980.
* Bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the 16-bit word:
* Bits 0-4: Day, valid range 1-31 inclusive.
* Bits 5-8: Month, 1=January, valid range 1-12 inclusive.
* Bits 9-15: Count of years from 1980, valid range 0-127 inclusive (1980-2107).
* All parameter must be in the valid range, no conversion will be done.
* @param dayOfMonth last access day.
* @param month last access month.
* @param year last access year.
*/
public void setLastAccessDate(int dayOfMonth, int month, int year)
{
lastAccessDate = 0;
lastAccessDate = (dayOfMonth);
lastAccessDate |= (month << 5);
lastAccessDate |= (year << 9);
} //end setLastAccessDate(...)
/**
* Set the last access date with the actual time.
*/
public void setLastAccessDate()
{
setLastAccessDate( calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.MONTH)+1,
calendar.get(Calendar.YEAR)-1980);
} //end lastAccessDate()
/**
* Set the last write date to the actual date. A date stamp is a 16-bit field
* that that is basically a date relative to the MS-DOS epoch of 01/01/1980.
* Bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the 16-bit word:
* Bits 0-4: Day, valid range 1-31 inclusive.
* Bits 5-8: Month, 1=January, valid range 1-12 inclusive.
* Bits 9-15: Count of years from 1980, valid range 0-127 inclusive (1980-2107).
* All parameter must be in the valid range, no conversion will be done.
* @param dayOfMonth last write day.
* @param month last write month.
* @param year last write year.
*/
public void setLastWriteDate(int dayOfMonth, int month, int year)
{
writeDate = 0;
writeDate = (dayOfMonth);
writeDate |= (month << 5);
writeDate |= (year << 9);
} //end setLastAccessDate(...)
/**
* Set the last write date with the actual date.
*/
public void setLastWriteDate()
{
setLastAccessDate( calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.MONTH)+1,
calendar.get(Calendar.YEAR)-1980);
setLastWriteDate( calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.MONTH)+1,
calendar.get(Calendar.YEAR)-1980);
}
/**
* Set the last write time to the given parameters. A time stamp is a 16-bit field
* that has a granularity of 2 seconds. Bit 0 is the LSB of the 16-bit word,
* bit 15 is the MSB of the 16-bit word.
* Bits 0-4: 2-second count, valid range 0-29 inclusive (0-58 seconds).
* Bits 5-10: Minutes , valid value range 0-59 inclusive.
* Bits 11-15: Hours, valid range 0-23 inclusive
* All parameter must be in the valid range, no conversion will be done.
* @param hour last write time hour.
* @param minute last write time minute.
* @param second last write time second.
*/
public void setLastWriteTime(int hour, int minute, int second)
{
writeTime = 0;
writeTime = second >> 1;
writeTime |= (minute << 5);
writeTime |= (hour << 11);
} //end setLastWriteTime(...)
/**
* Set the last write time with the actual time.
*/
public void setLastWriteTime()
{
setLastWriteTime( calendar.get(Calendar.HOUR) - 1,
calendar.get(Calendar.MINUTE),
calendar.get(Calendar.SECOND) >> 1
);
} //end setLastWriteTime()
/**
* Return the representation of this class as byte array.
* @return the directory entry as byte array. The length is a multiple
* of 32 byte.
*/
public byte[] getByte()
{
byte[] arr = new byte[32*numOfEntries];
//long name
if (arr.length > 32)
{
byte[] temp;
byte checksum = (byte)checksum(shortName.getBytes());
int longNameIndex = 0; //index inside the string long name
for (int i=1; i < numOfEntries; i++)
{
//set order number
arr[arr.length - 32*(i+1)] = (byte)i;
//set attribute long name
arr[arr.length - 32*(i+1) + 11] = ATTR_LONG_NAME;
//set type
arr[arr.length - 32*(i+1) + 12] = (byte)0; //indicates that this long directory entry is a sub-component of a long name
//set checksum
arr[arr.length - 32*(i+1) + 13] = checksum;
//set first cluster low
arr[arr.length - 32*(i+1) + 26] = (byte)0; //must be zero
arr[arr.length - 32*(i+1) + 27] = (byte)0;
try
{
//set the first 5 characters
if (longNameIndex+5 <= longName.length())
{
temp = longName.substring(longNameIndex, longNameIndex+5).getBytes("UTF-16LE");
System.arraycopy(temp, 0, arr, arr.length - 32*(i+1) + 1, temp.length);
}
else
{
temp = longName.substring(longNameIndex, longName.length()).getBytes("UTF-16LE");
System.arraycopy(temp, 0, arr, arr.length - 32*(i+1) + 1, temp.length);
int index = arr.length - 32*(i+1) + 1 + temp.length;
arr[index] = (byte)0;
arr[++index] = (byte)0;
for(index++; index < 11; index++)
arr[index] = (byte)0xFF;
for(index=14; index < 26; index++)
arr[index] = (byte)0xFF;
for(index=28; index < 32; index++)
arr[index] = (byte)0xFF;
break;
}
longNameIndex += 5;
if (longNameIndex+6 <= longName.length())
{
//set the next 6 charecters
temp = longName.substring(longNameIndex, longNameIndex+6).getBytes("UTF-16LE");
System.arraycopy(temp, 0, arr, arr.length - 32*(i+1) + 14, temp.length);
}
else
{
temp = longName.substring(longNameIndex, longName.length()).getBytes("UTF-16LE");
System.arraycopy(temp, 0, arr, arr.length - 32*(i+1) + 14, temp.length);
int index = arr.length - 32*(i+1) + 14 + temp.length;
arr[index] = (byte)0;
arr[++index] = (byte)0;
for(index++; index < 26; index++)
arr[index] = (byte)0xFF;
for(index=28; index < 32; index++)
arr[index] = (byte)0xFF;
break;
}
longNameIndex += 6;
if(longNameIndex+2 <= longName.length())
{
//set the next 2 characters
temp = longName.substring(longNameIndex, longNameIndex+2).getBytes("UTF-16LE");
System.arraycopy(temp, 0, arr, arr.length - 32*(i+1) + 28, temp.length);
}
else
{
temp = longName.substring(longNameIndex, longName.length()).getBytes("UTF-16LE");
System.arraycopy(temp, 0, arr, arr.length - 32*(i+1) + 28, temp.length);
int index = arr.length - 32*(i+1) + 28 + temp.length;
arr[index] = (byte)0;
arr[++index] = (byte)0;
for(index++; index < 32; index++)
arr[index] = (byte)0xFF;
break;
}
longNameIndex += 2;
}
catch(UnsupportedEncodingException e)
{
System.out.println(e);
continue;
}
}
//set last long entry flag
arr[0] |= (byte)0x40;
} //end long name
//short name
System.arraycopy(shortName.getBytes(), 0, arr, arr.length - 32 + 0, 11);
//attr
arr[arr.length - 32 + 11] = (byte)attributes;
//ntReserved
arr[arr.length - 32 + 12] = (byte)ntReserved;
//creationTimeTenth
arr[arr.length - 32 + 13] = (byte)creationTimeTenth;
//creationTime
arr[arr.length - 32 + 14] = (byte)creationTime;
arr[arr.length - 32 + 15] = (byte)(creationTime >> 8);
//creationDate
arr[arr.length - 32 + 16] = (byte)creationDate;
arr[arr.length - 32 + 17] = (byte)(creationDate >> 8);
//lastAccessDate
arr[arr.length - 32 + 18] = (byte)(lastAccessDate);
arr[arr.length - 32 + 19] = (byte)(lastAccessDate >> 8);
//firstClusterHI
arr[arr.length - 32 + 20] = (byte)firstClusterHI;
arr[arr.length - 32 + 21] = (byte)(firstClusterHI >> 8);
//writeTime
arr[arr.length - 32 + 22] = (byte)writeTime;
arr[arr.length - 32 + 23] = (byte)(writeTime >> 8);
//writeDate
arr[arr.length - 32 + 24] = (byte)writeDate;
arr[arr.length - 32 + 25] = (byte)(writeDate >> 8);
//firstClusterLO
arr[arr.length - 32 + 26] = (byte)(firstClusterLO);
arr[arr.length - 32 + 27] = (byte)(firstClusterLO >> 8);
//fileSize
arr[arr.length - 32 + 28] = (byte)(fileSize);
arr[arr.length - 32 + 29] = (byte)(fileSize >> 8);
arr[arr.length - 32 + 30] = (byte)(fileSize >> 16);
arr[arr.length - 32 + 31] = (byte)(fileSize >> 24);
return arr;
} //end getByte()
/**
* Return how much 32 byte entries are needed to store all the information of this directory entry.
* @return the number of 32 byte entries of this directory entry.
*/
public int getNumOfEntries()
{
return numOfEntries;
} //end getNumOfEntries()
/**
* Return the cluster number.
* @return cluster number.
*/
public long getClusterNumber()
{
return ((long)(firstClusterHI) << 16) | firstClusterLO;
} //end getClusterNumber()
/**
* Return the length of the file associated with this directory entry. If
* it's a directory the file length is 0.
* @return the length of the file.
*/
public long length()
{
return fileSize;
} //end length()
} //end inner class DirectoryStructure
/**
* Creates an instance of this object. The constructor will
* create a new directory structure. Use this constructor
* for a format process.
* @param device instance of FATDevice.
* @param bpb instance of BPB.
*/
public DIR(FATDevice device, BPB bpb)
{
this.device = device;
this.bpb = bpb;
//initialize calendar with actual time
calendar.setTime(new Date());
sectorBuffer = new byte[bpb.BytsPerSec];
clusterBuffer = new byte[bpb.BytsPerSec*bpb.SecPerClus];
//this value is valid for all three FAT's
firstRootDirSecNum = bpb.getFirstRootDirSecNum();
//this value is only valid in case of FAT12 or FAT16
numRootDirSectors = bpb.getRootDirSectors();
//create an empty directory structure with only the root directory.
//In case of FAT12 or FAT16 the byte array contains as much sector as needed to store the
//the bpb.RootEntCnt.
//In case of FAT32 the byte array has a size of one cluster.
rootDirectory = getDir(numRootDirSectors, firstRootDirSecNum);
if (device.getFatType() != FAT.FAT32)
{
for (int i=(int)firstRootDirSecNum; i < firstRootDirSecNum + numRootDirSectors; i++)
{
System.arraycopy(
rootDirectory,
(int)(i - firstRootDirSecNum)*bpb.BytsPerSec,
sectorBuffer,
0,
bpb.BytsPerSec
);
writeSector(sectorBuffer, i);
}
}
else
{
//write cluster to disk
writeCluster(device.getFirstSectorNumberOfCluster(firstRootDirSecNum), rootDirectory);
//mark this cluster number as used in FAT
device.setFatContent(firstRootDirSecNum, FAT.EOC_MARK32);
}
} //end constructor format
/**
* Creates an instance of this object. The constructor will
* boot the directory structure.
* @param device instance of FATDevice.
* @param bpb instance of BPB.
* @param fatType the type of FAT.
*/
public DIR(FATDevice device, BPB bpb, byte fatType)
{
this.device = device;
this.bpb = bpb;
//initialize calendar with actual time
calendar.setTime(new Date());
sectorBuffer = new byte[bpb.BytsPerSec];
clusterBuffer = new byte[bpb.BytsPerSec*bpb.SecPerClus];
//this value is valid for all FAT's
firstRootDirSecNum = bpb.getFirstRootDirSecNum();
//this value is only valid for FAT32
numRootDirSectors = bpb.getRootDirSectors();
if (fatType != FAT.FAT32)
{
rootDirectory = new byte[(int)numRootDirSectors*bpb.BytsPerSec];
for (int i=(int)firstRootDirSecNum; i < firstRootDirSecNum + numRootDirSectors; i++)
{
sectorBuffer = readSector(i);
System.arraycopy(sectorBuffer, 0, rootDirectory, (int)(i - firstRootDirSecNum)*bpb.BytsPerSec, bpb.BytsPerSec);
}
if (FileSystem.debug)
{
System.out.println("\n\tDirectory\n");
Arrays.printHexArray(rootDirectory, firstRootDirSecNum * bpb.BytsPerSec, System.out);
}
}
else
{
//read all cluster of the cluster chain beginning at the first root directory cluster number
LinkedList clusterList = new LinkedList();
LinkedList clusterNumberList = new LinkedList(); //only for debug and information purposes.
long clusterNumber = firstRootDirSecNum;
do
{
clusterNumberList.addLast(new Long(clusterNumber));
clusterList.addLast(readCluster(device.getFirstSectorNumberOfCluster(clusterNumber)));
clusterNumber = device.getFatContent(clusterNumber);
}while(!device.isLastCluster(clusterNumber));
int clusterLength = ((byte[])clusterList.get(0)).length;
rootDirectory = new byte[clusterLength*clusterList.size()];
for (int i=0; i < clusterList.size(); i++)
{
System.arraycopy((clusterList.get(i)), 0, rootDirectory, i*clusterLength, clusterLength);
if (FileSystem.debug)
Arrays.printHexArray(
((byte[])clusterList.get(i)),
device.getFirstSectorNumberOfCluster(((Long)clusterNumberList.get(i)).longValue())*bpb.BytsPerSec,
System.out
);
}
}
} //end constructor boot
/**
* Return a byte array of the directory. Use this method only if you
* format your file-system. In case of FAT12 or FAT16 the returned byte
* array consists of enough sectors to store the BPB.RootEntCnt. In case of FAT32
* the returned byte array is as big as a cluster.
* @param rootDirSectors the number of the sectors of the root directory.
* @param firstRootDirSecNum the cluster number stored at the root directory
* entry. In case of FAT12 or fAT16 this value should be 0. In case of FAT32
* this value must be cluster number where the root directory is stored.
* @return the root directory as a byte array.
*/
protected byte[] getDir(long rootDirSectors, long firstRootDirSecNum)
{
int length = 0;
if (bpb.getFatType() != FAT.FAT32)
length = (int)rootDirSectors*bpb.BytsPerSec;
else
length = bpb.SecPerClus*bpb.BytsPerSec;
byte[] dir = new byte[length];
//first entry is root directory
byte[] rootEntry = createRootDirectory((byte)(ATTR_VOLUME_ID | ATTR_ARCHIVE), firstRootDirSecNum);
System.arraycopy(rootEntry, 0, dir, 0, 32);
//all other entrys marked as free
for (int i=32; i < length; i += 32)
dir[i] = FREE_ENTRY_AND_NEXTS_FREE;
return dir;
} //end getDir(int rootDirSectors)
/**
* Read the cluster indexed by cluster number.
* @param clusterNumber the number of the cluster.
* @return the cluster as byte array.
*/
protected byte[] readCluster(long clusterNumber)
{
return device.readCluster(clusterNumber);
} //end readCluster(long clusterNumber)
/**
* Read the sector indexed by sector number.
* @param sectorNumber the number of the sector.
* @return the sector as byte array.
*/
protected byte[] readSector(long sectorNumber)
{
byte b[] = new byte[bpb.BytsPerSec];
if (!device.readSector(b,sectorNumber))
return null;
else
return b;
} //end readSector(long sectorNumber)
/**
* Write the sector indexed by sector number to disk.
* @param sector the sector to write.
* @param sectorNumber the number of the sector.
*/
protected void writeSector(byte[] sector, long sectorNumber)
{
device.writeSector(sector, sectorNumber);
} //end writeSector(byte[] sector, long sectorNumber)
/**
* Write the cluster indexed by cluster number to disk.
* @param cluster the cluster to write.
* @param clusterNumber the number of the cluster.
*/
protected void writeCluster(long clusterNumber, byte[] cluster)
{
device.writeCluster(clusterNumber, cluster);
} //end writeCluster(long clusterNumber, byte[] cluster)
/**
* Write the directory entry of dirEntInf at the positions given by dirEntInf to disk.
* @param dirEntInf information about the directory entry.
* @throws DirectoryException in case the directory entry can not be written.
*/
protected void writeDirectoryEntry(DirectoryEntryInformation dirEntInf) throws DirectoryException
{
//check for boundary case
if (dirEntInf.directoryEntryIndex + dirEntInf.directoryEntry.length >= dirEntInf.cluster.length)
{ //boundary case
//write the first part
System.arraycopy( dirEntInf.directoryEntry,
0,
dirEntInf.cluster,
(int)dirEntInf.directoryEntryIndex,
dirEntInf.cluster.length - (int)dirEntInf.directoryEntryIndex);
if (dirEntInf.isRoot && device.getFatType() != FAT.FAT32)
writeSector(dirEntInf.cluster, dirEntInf.clusterNumber);
else
writeCluster(device.getFirstSectorNumberOfCluster(dirEntInf.clusterNumber), dirEntInf.cluster);
long clusterNumber = dirEntInf.clusterNumber;
int index = (int)(dirEntInf.cluster.length - dirEntInf.directoryEntryIndex);
if (dirEntInf.isRoot && device.getFatType() != FAT.FAT32) //root directory
{
long lastRootDirClusterNumber = firstRootDirSecNum + MyMath.roundUp(numRootDirSectors/(float)bpb.SecPerClus);
for (;;)
{
clusterNumber++;
if (clusterNumber >= lastRootDirClusterNumber)
throw new DirectoryException("");
byte[] cluster = readSector(clusterNumber);
int length = dirEntInf.directoryEntry.length-index >= cluster.length ? cluster.length : dirEntInf.directoryEntry.length - index;
System.arraycopy(dirEntInf.directoryEntry, index, cluster, 0, length);
writeSector(cluster, clusterNumber);
index += length;
if (index >= dirEntInf.directoryEntry.length)
return; //we are done
}
}
else //not root directory or FAT32
{
do
{
clusterNumber = device.getFatContent(clusterNumber);
if (device.isLastCluster(clusterNumber))
throw new DirectoryException("");
byte[] cluster = readCluster(device.getFirstSectorNumberOfCluster(clusterNumber));
int length = dirEntInf.directoryEntry.length - index >= cluster.length ?
cluster.length :
dirEntInf.directoryEntry.length - index;
System.arraycopy(dirEntInf.directoryEntry, index, cluster, 0, length);
writeCluster(device.getFirstSectorNumberOfCluster(clusterNumber), cluster);
index += length;
if (index >= dirEntInf.directoryEntry.length)
return; //we are done
}while(true);
}
}
else //no boundary
{
System.arraycopy(dirEntInf.directoryEntry, 0, dirEntInf.cluster, (int)dirEntInf.directoryEntryIndex, dirEntInf.directoryEntry.length);
if (dirEntInf.isRoot && device.getFatType() != FAT.FAT32)
writeSector(dirEntInf.cluster, dirEntInf.clusterNumber); //root directory is only accessible by sector
else
writeCluster(device.getFirstSectorNumberOfCluster(dirEntInf.clusterNumber), dirEntInf.cluster);
}
} //end writeDirectoryEntry(...)
/**
* Write the directory entry at the position given by freeInformation to disk.
* @param freeInformation information about directory entry.
* @param directoryEntry the directory entry to write.
*/
private void writeDirectoryEntry(FreeInformation freeInformation, byte[] directoryEntry)
{
List freeClusters = freeInformation.freeClusters;
//copy first part of directory entry and write to disk
byte[] cluster;
if (freeInformation.cluster != null)
cluster = freeInformation.cluster;
else
{
if (freeInformation.isRoot && device.getFatType() != FAT.FAT32)
cluster = readSector(((Long)freeClusters.get(0)).longValue());
else
cluster = readCluster(((Long)freeClusters.get(0)).longValue());
}
System.arraycopy(
directoryEntry,
0,
cluster,
freeInformation.freeEntryIndex,
(directoryEntry.length + freeInformation.freeEntryIndex > cluster.length) ?
cluster.length - freeInformation.freeEntryIndex :
directoryEntry.length
);
if (freeInformation.isRoot && device.getFatType() != FAT.FAT32)
writeSector(cluster, ((Long)freeClusters.get(0)).longValue());
else
writeCluster(((Long)freeClusters.get(0)).longValue(), cluster);
int directoryEntryIndex = cluster.length - freeInformation.freeEntryIndex;
int restLength = directoryEntry.length - directoryEntryIndex;
//if directory entry spans over cluster boundary copy the rest of
//directory entry and write the remaining cluster
for (int i=1; i < freeClusters.size(); i++)
{
if (freeInformation.isRoot && device.getFatType() != FAT.FAT32)
cluster = readSector(((Long)freeClusters.get(i)).longValue());
else
cluster = readCluster(((Long)freeClusters.get(i)).longValue());
System.arraycopy(
directoryEntry, //source
directoryEntryIndex, //source index
cluster, //destination
0, //destination index
((cluster.length > restLength) ? restLength : cluster.length) //length
);
if (freeInformation.isRoot && device.getFatType() != FAT.FAT32)
writeSector(cluster, ((Long)freeClusters.get(i)).longValue());
else
writeCluster(((Long)freeClusters.get(i)).longValue(), cluster);
directoryEntryIndex += cluster.length;
restLength -= cluster.length;
}
} //end writeDirectoryEntry(FreeInformation freeInformation, byte[] directoryEntry)
/**
* Create a new entry in the directory.
* In case the directory structure to the new entry doesn't exist a FileDoesntExist exception
* will be thrown. In case there is already an entry with the same name a NameAllreadyExists
* exception will be thrown. In case there is not enough memory to store the entry
* a NotEnoughMemory exception is thrown.
* @param directoryName is the name of the new file.
* @return the cluster number stored at the new entry.
* @throws DirectoryException in case the entry couldn't be created.
*/
public long createFile(String directoryName) throws DirectoryException
{
return createFile(directoryName, ATTR_ARCHIVE, 0);
} //end createFile(String directoryName)
/**
* Create a new entry in the directory. The following values are initialized to the new entry:
* The name is the last component of directoryName, attribute, and fileSize.
* There will be enough space ordered to suffice the given size of the file.
* In case the directory structure to the new entry doesn't exist a FileDoesntExist exception
* will be thrown. In case there is already an entry with the same name a NameAllreadyExists
* exception will be thrown. In case there is not enough memory to store the entry or to suffice
* the given size of the file a NotEnoughMemory exception is thrown.
* @param directoryName is the name of the new file.
* @param fileSize is the size of the file of the new entry.
* @return the cluster number stored at the new entry.
* @throws DirectoryException in case the entry couldn't be created.
*/
public long createFile(String directoryName, long fileSize) throws DirectoryException
{
return createFile(directoryName, ATTR_ARCHIVE, fileSize);
} //end createFile(String directoryName, long fileSize)
/**
* Create a new entry in the directory. The following values are initialized to the new entry:
* The name is the last component of directoryName, attribute, and fileSize.
* There will be enough space ordered to suffice the given size of the file.
* In case the directory structure to the new entry doesn't exist a FileDoesntExist exception
* will be thrown. In case there is already an entry with the same name a NameAllreadyExists
* exception will be thrown. In case there is not enough memory to store the entry or to suffice
* the given size of the file a NotEnoughMemory exception is thrown.
* @param directoryName is the name of the new file.
* @param attribute is the attribute of the new entry.
* @param fileSize is the size of the file of the new entry.
* @return the cluster number stored at the new entry.
* @throws DirectoryException in case the entry couldn't be created.
*/
public long createFile(String directoryName, byte attribute, long fileSize) throws DirectoryException
{
ClusterTuple ct = getCluster(StringOperations.extractPath(directoryName));
return createFile(directoryName, attribute, fileSize, ct);
} //end createFile(String directoryName, byte attribute, long fileSize)
/**
* Create a new entry in the directory. The following values are initialized to the new entry:
* The name is the last component of directoryName, attribute, and fileSize.
* The new entry is stored at the position given by clusterTuple. There will be enough space
* ordered to suffice the given size of the file.
* In case the directory structure to the new entry doesn't exist a FileDoesntExist exception
* will be thrown. In case there is already an entry with the same name a NameAllreadyExists
* exception will be thrown. In case there is not enough memory to store the entry or to suffice
* the given size of the file a NotEnoughMemory exception is thrown.
* @param directoryName is the name of the new file.
* @param attribute is the attribute of the new entry.
* @param fileSize is the size of the file of the new entry.
* @param clusterTuple contains information about the position the new entry is stored.
* @return the cluster number stored at the new entry.
* @throws DirectoryException in case the entry couldn't be created.
*/
private long createFile(String directoryName, byte attribute, long fileSize, ClusterTuple clusterTuple) throws DirectoryException
{
//get enough space to store the data of the file
List freeClusterList = device.getFreeClusters(
MyMath.roundUp(fileSize / ((float)bpb.SecPerClus*bpb.BytsPerSec))
);
return createFile(
directoryName,
attribute,
fileSize,
(freeClusterList != null && freeClusterList.size() > 0) ?
((Long)freeClusterList.get(0)).longValue() :
0,
clusterTuple
);
} //end createFile(String directoryName, byte attribute, long fileSize, ClusterTuple clusterTuple)
/**
* Create a new entry in the directory. The following values are initialized to the new entry:
* The name is the last component of directoryName, attribute, fileSize, and clusterNumber.
* The new entry is stored at the position given by clusterTuple. There will be enough space
* ordered to suffice the given size of the file.
* In case the directory structure to the new entry doesn't exist a FileDoesntExist exception
* will be thrown. In case there is already an entry with the same name a NameAllreadyExists
* exception will be thrown. In case there is not enough memory to store the entry or to suffice
* the given size of the file a NotEnoughMemory exception is thrown.
* @param directoryName is the name of the new file.
* @param attribute is the attribute of the new entry.
* @param fileSize is the size of the file of the new entry.
* @param clusterNumber is the number of the cluster of the new entry.
* @param clusterTuple contains information about the position the new entry is stored.
* @return the cluster number stored at the new entry.
* @throws DirectoryException in case the entry couldn't be created.
*/
private long createFile(String directoryName,
byte attribute,
long fileSize,
long clusterNumber,
ClusterTuple clusterTuple) throws DirectoryException
{
if (clusterTuple == null)
throw new FileDoesntExist("The directory structure to the file "+directoryName+" doesn't exist.");
String fileName = StringOperations.extractFileName(directoryName);
DirectoryEntryInformation dirEntInf = findDirectoryEntry(
clusterTuple.clusterNumber,
fileName,
clusterTuple.isRoot
);
if (dirEntInf != null && dirEntInf.directoryEntry != null)
throw new NameAllreadyExists(directoryName);
DirectoryStructure dirEntry = new DirectoryStructure(
fileName, //name of the file that is stored at the directory entry
clusterNumber, //number of cluster that is stored at the directory entry
attribute, //attribute that is stored at the directory entry
fileSize, //size of file that is stored at directory entry
//cluster number at which the directory entry is stored
clusterTuple.clusterNumber,
clusterTuple.isRoot //belongs the clusterNumber to the root directory?
);
//get enough space to store the entry
FreeInformation freeInformation = getFreeDirectoryEntryIndex(
clusterTuple.clusterNumber,
dirEntry.getNumOfEntries(),
clusterTuple.isRoot
);
writeDirectoryEntry(freeInformation, dirEntry.getByte());
return dirEntry.getClusterNumber();
} //end createFile(String directoryName, byte attribute, long fileSize, long clusterNumber, ClusterTuple clusterTuple)
/**
* Return the directory entry information about directoryName or null in case
* the file doesn't exist.
* @param directoryName the name of the directory.
* @return information about the searched directory entry or null in case
* it doesn't exist.
*/
public DirectoryEntryInformation findDirectoryEntry(String directoryName)// throws FileDoesntExist
{
ClusterTuple clusterTuple = getCluster(StringOperations.extractPath(directoryName));
if (clusterTuple == null)
return null;
//extract the name from directoryName
String fileName = StringOperations.extractFileName(directoryName);
return findDirectoryEntry(clusterTuple.clusterNumber, fileName, clusterTuple.isRoot);
} //end findDirectoryEntry(String directoryName)
/**
* Find the directory entry with the given fileName in the cluster chain started at the given
* cluster number.
* @param clusterNumber the first cluster number of the cluster chain the entry should be searched.
* In case the clusterNumber belongs to the root directory only by FAT12 and FAT16 it has to be
* direct otherwise indirect.
* @param fileName the name of the file without the path or device name.
* @param isRoot indicates if the cluster number belongs to the root directory.
* @return information about the searched directory entry and the entry itself or null in case
* the searched entry doesn't exist.
*/
protected DirectoryEntryInformation findDirectoryEntry(long clusterNumber, String fileName, boolean isRoot)
{
Iterator entriesIterator = new EntriesFilterIterator(
new ListClusterChainEntriesIterator(
device,
clusterNumber,
isRoot),
LIST_ALL_EXCEPT_FREE
);
while (entriesIterator.hasNext())
{
DirectoryEntryInformation dirEntInf = (DirectoryEntryInformation)entriesIterator.next();
if (getName(dirEntInf.directoryEntry).equals(fileName))
return dirEntInf;
}
return null;
} //end findDirectoryEntry(byte[] cluster, String fileName)
/**
* Return the information about the given path, that are the first cluster number of the cluster
* chain which belongs to the given path and a flag which indicates if the path points to
* the root directory or not. For example if path equals "\\" or "" path points to the root directory
* in this case the first cluster number is the first cluster number of the root directory.
* if the path points to an other directory the first cluster number is the cluster number
* of the last directory from path of directory entry.
* In case the path doesn't exist or the last component is not a directory null is returned.
* @param path the path through the directory structure.
* @return information about the path or null in case the path doesn't exist or it's not a directory.
*/
private ClusterTuple getCluster(String path)
{
if (path.equals(System.getProperty("file.separator")) || path.equals("")) //return root
return new ClusterTuple(firstRootDirSecNum, true);
StringTokenizer stringTokenizer = new StringTokenizer(path, System.getProperty("file.separator"));
if (!stringTokenizer.hasMoreTokens())
return new ClusterTuple(firstRootDirSecNum, true);
String str = stringTokenizer.nextToken();
//start the search with the cluster of the root directory entry
long clusterNumber = firstRootDirSecNum;
DirectoryEntryInformation dirEntInf = findDirectoryEntry(clusterNumber, str, true);
if (dirEntInf == null)
return null;
byte[] directoryEntry = dirEntInf.directoryEntry;
if (isDirectory(directoryEntry))
{
if (stringTokenizer.hasMoreTokens())
{
//continue search, but from now on with cluster chain except the root directory
return getCluster(getClusterNumber(directoryEntry), stringTokenizer);
}
else
{
//we found the entry and it is a directory
return new ClusterTuple(getClusterNumber(directoryEntry), false);
}
}
//sorry, but searched entry is not a directory
return null;
} //end getCluster(String fileName)
/**
* Is used by getCluster(String path). The method follows the path through the directory structure
* given by cluster number and the string tokenizer. See documentation of getCluster(String path)
* for details.
* @param fatIndex the index of the FAT.
* @param stringTokenizer string tokenizer which contains the directory path.
* @return information about the path or null in case the path doesn't exist or it's not a directory.
*/
private ClusterTuple getCluster(long fatIndex, StringTokenizer stringTokenizer)
{
if (!stringTokenizer.hasMoreTokens())
return null;
String str = stringTokenizer.nextToken();
DirectoryEntryInformation dirEntInf = findDirectoryEntry(fatIndex, str, false);
if (dirEntInf == null)
return null;
byte[] directoryEntry = dirEntInf.directoryEntry;
if (isDirectory(directoryEntry) && stringTokenizer.hasMoreTokens())
{
return getCluster(getClusterNumber(directoryEntry), stringTokenizer);
}
else if (stringTokenizer.hasMoreTokens())
//it's not a directory but there are more tokens
return null;
if (isDirectory(directoryEntry))
{
//there are no more tokens and it is a directory
return new ClusterTuple(getClusterNumber(directoryEntry), false);
}
else //there are no more tokens and it is not a directory
{
return null;
}
} //end getCluster((long clusterNumber, StringTokenizer stringTokenizer)
/**
* Create a new directory (folder). One cluster is allocated to the directory
* (unless it is the root directory on a FAT16/FAT12 volume). All bytes in that
* cluster are initialized to 0. There are two special entries in the first two
* 32-byte
* directory entries of the directory (the first two 32 bytes entries in the data
* region of the cluster that is allocated). The first entry is the dot entry, the
* second entry is the dot dot entry. The dot entry points to itself and the
* dot dot entry points to the starting cluster of the parent of this directory.
* @param directoryName the name of the directory.
* @throws DirectoryException in case the directory couldn't be created.
*/
public void createDirectory(String directoryName) throws DirectoryException
{
createDirectory(directoryName, ATTR_DIRECTORY);
} //end createDirectory(String directoryName)
/**
* Create a new directory (folder). One cluster is allocated to the directory
* (unless it is the root directory on a FAT16/FAT12 volume). All bytes in that
* cluster are initialized to 0. There are two special entries in the first two 32-byte
* directory entries of the directory (the first two 32 bytes entries in the data
* region of the cluster that is allocated). The first entry is the dot entry, the
* second entry is the dot dot entry. The dot entry points to itself and the
* dot dot entry points to the starting cluster of the parent of this directory.
* @param directoryName the name of the directory.
* @param attributes the attributes for the new entry.
* @throws DirectoryException in case the directory couldn't be created.
*/
public void createDirectory(String directoryName, byte attributes) throws DirectoryException
{
createDirectory(
getCluster(StringOperations.extractPath(directoryName)),
directoryName,
attributes,
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
calendar.get(Calendar.SECOND),
calendar.get(Calendar.MILLISECOND),
calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.MONTH) + 1,
calendar.get(Calendar.YEAR)
);
} //end createDirectory(String directoryName, byte attributes)
/**
* Create a new directory (folder). One cluster is allocated to the directory
* (unless it is the root directory on a FAT16/FAT12 volume). All bytes in that
* cluster are initialized to 0. There are two special entries in the first two 32-byte
* directory entries of the directory (the first two 32 bytes entries in the data
* region of the cluster that is allocated). The first entry is the dot entry, the
* second entry is the dot dot entry. The dot entry points to itself and the
* dot dot entry points to the starting cluster of the parent of this directory.
* @param clusterTuple contains information where the new directory should be stored.
* @param directoryName the name of the directory.
* @param attributes the attributes for the new entry.
* @param hourOfDay the hour of the time, valid range from 1 to 24.
* @param minutes the minute of the time, valid range from 0 to 59.
* @param seconds the seconds of the time, valid range from 0 to 59.
* @param milliseconds the milliseconds of the time, valid range from 0 to 999.
* @param dayOfMonth the day of month, valid range from 1 to 31.
* @param monthOfYear the month of the year, valid range from 1 to 12.
* @param countOfYears the count of years, valid range from 1980 to 2107.
* @throws DirectoryException in case the directory couldn't be created.
*/
private void createDirectory(
ClusterTuple clusterTuple,
String directoryName,
byte attributes,
int hourOfDay,
int minutes,
int seconds,
int milliseconds,
int dayOfMonth,
int monthOfYear,
int countOfYears
) throws DirectoryException
{
//get one cluster of space for the new directory
List freeClusterList = device.getFreeClusters(1);
long fatFreeIndex = ((Long)freeClusterList.get(0)).longValue();
//create the directory entry, this can be done with createFile(...)
createFile(directoryName, attributes, 0, fatFreeIndex, clusterTuple);
//now the content of the directory will be created, that is the dot and dot dot entry.
//cluster number where the directory entry will be stored
long clusterNumberOfDirectoryEntry = (
(clusterTuple.isRoot &&device.getFatType() != FAT.FAT32) ?
clusterTuple.clusterNumber :
device.getFirstSectorNumberOfCluster(clusterTuple.clusterNumber)
);
//cluster number where the directory with dot and dot dot entry will be created
long clusterNumberOfDirectory = device.getFirstSectorNumberOfCluster(fatFreeIndex);
//initialize the cluster with 0 values
byte[] clusterBuffer = new byte[bpb.SecPerClus*bpb.BytsPerSec];
for (int i=0; i < clusterBuffer.length; i++)
clusterBuffer[i] = 0;
//create Dot-Entry
DirectoryStructure dot = null, dotdot = null;
try
{
dot = new DirectoryStructure( ". ",
attributes,
hourOfDay,
minutes,
seconds,
milliseconds,
dayOfMonth,
monthOfYear,
countOfYears,
clusterNumberOfDirectory, //cluster number stored in directory entry
//points to itself
0, //file size
clusterNumberOfDirectory, //start cluster number where the whole entry is stored
false //the clusterNumberOfDirectory does not belong to the root directory
);
//create Dot-Dot-Entry
dotdot = new DirectoryStructure(".. ",
attributes,
hourOfDay,
minutes,
seconds,
milliseconds,
dayOfMonth,
monthOfYear,
countOfYears,
//dayOfYear,
clusterNumberOfDirectoryEntry, //pointer to parent directory
0, //file size
clusterNumberOfDirectory, //start cluster number where the whole entry is stored
false //the clusterNumberOfDirectory does not belong to the root directory
);
}
catch(Exception e)
{
System.out.println(e);
}
byte[] directoryEntry = dot.getByte();
System.arraycopy(directoryEntry, 0, clusterBuffer, 0, directoryEntry.length);
directoryEntry = dotdot.getByte();
System.arraycopy(directoryEntry, 0, clusterBuffer, directoryEntry.length, directoryEntry.length);
//write cluster because it's a directory and not the root directory
writeCluster(clusterNumberOfDirectory, clusterBuffer);
} //end createDirectory(ClusterTuple clusterTuple,String directoryName,byte attributes,int hourOfDay,int minutes,int seconds,int milliseconds,int dayOfMonth,int monthOfYear,int countOfYears,int dayOfYear)
/**
* Create root directory entry.
* @param attributes attributes for the root directory.
* @param firstRootDirSecNum the cluster number stored at the root directory
* entry. In case of FAT12 or fAT16 this value should be 0. In case of FAT32
* this value must be cluster number where the root directory is stored.
* @return root directory entry as byte array.
*/
private byte[] createRootDirectory(byte attributes, long firstRootDirSecNum)
{
DirectoryStructure dirStructure = null;
try
{
if (device.getFatType() != FAT.FAT32)
dirStructure = new DirectoryStructure(
System.getProperty("file.separator"),
attributes,
firstRootDirSecNum, //cn where the directory is stored
true
);
else
dirStructure = new DirectoryStructure(
System.getProperty("file.separator"),
firstRootDirSecNum, //cn stored at the entry
attributes, //attributes
0, //fileSize
firstRootDirSecNum, //cn where the directory is stored
false
);
}
catch (Exception e)
{
System.out.println(e);
}
return dirStructure.getByte();
} //end createRootDirectory
/**
* Check if there exists a file with the given directory name.
* @param directoryName the name of the file.
* @return true in case such a file exists.
*/
public boolean exists(String directoryName)
{
try
{
return getDirectoryEntry(directoryName) != null;
}
catch(FileDoesntExist e)
{
return false;
}
} //end exists(String directoryName)
/**
* Return the index to the first free directory entry that is found in the cluster chain
* started at cluster number and where the next (numOfFreeEntries - 1) are also free.
* If no such entry exists a DirectoryException is thrown.
* @param clusterNumber the first cluster number of the cluster chain.
* @param numOfFreeEntries the number of free continuous entries.
* @param isRoot indicates if the cluster number belongs to the root directory.
* @return information about the searched free index.
* @throws DirectoryException in case no such entry exists.
*/
private FreeInformation getFreeDirectoryEntryIndex(long clusterNumber, int numOfFreeEntries, boolean isRoot) throws DirectoryException
{
return getFreeDirectoryEntryIndex(
(isRoot && device.getFatType() != FAT.FAT32) ?
readSector(clusterNumber) :
readCluster(device.getFirstSectorNumberOfCluster(clusterNumber)),
clusterNumber,
0,
numOfFreeEntries,
isRoot
);
} //end getFreeDirectoryEntryIndex(long clusterNumber, int numOfFreeEntries, boolean isRoot)
/**
* Is used by getFreeDirectoryEntryIndex(long clusterNumber, int numOfFreeEntries, boolean isRoot).
* See that documentation.
* @param cluster where the free entries are searched.
* @param clusterNumber cluster number of the given cluster.
* @param clusterIndex index inside the cluster at which the search starts.
* @param numOfFreeEntries the number of free entries that are searched.
* @param isRoot indicates if the cluster number belongs to the root directory.
* @return information about the searched free index.
* @throws DirectoryException in case no such entry exists.
*/
private FreeInformation getFreeDirectoryEntryIndex(
byte[] cluster,
long clusterNumber,
int clusterIndex,
int numOfFreeEntries,
boolean isRoot) throws DirectoryException
{
byte[] directoryEntry = new byte[32];
int counter;
int startIndex;
long startClusterNumber;
for (; clusterIndex < cluster.length; clusterIndex += 32)
{
System.arraycopy(cluster, clusterIndex, directoryEntry, 0, 32);
counter = 0;
//if we find a free entry there are different possibilities:
//1. this and the next free following entries are not enough to
//support this operation.
//2. this and the next following entries are enough to support this operation
//3. this and the next following entries of this cluster are not enough to
//support this operation:
//3.1 the entries of the following cluster are enough to support this operation
//3.2 the entries of the following cluster are not enough to support this operation.
if (isFree(directoryEntry))
{
//check if there are enough entries left on this cluster to support the operation
if (clusterIndex + 32*numOfFreeEntries <= cluster.length)
{
startIndex = clusterIndex;
do
{
counter++;
if (counter >= numOfFreeEntries)
{
List freeClusters = new LinkedList();
freeClusters.add(
new Long((isRoot && device.getFatType() != FAT.FAT32) ?
clusterNumber :
device.getFirstSectorNumberOfCluster(clusterNumber))
);
return new FreeInformation(startIndex, freeClusters, cluster, isRoot);
}
clusterIndex += 32;
System.arraycopy(cluster, clusterIndex, directoryEntry, 0, 32);
}
while(isFree(directoryEntry));
} //end if
else //this cluster contains not enough (free) entries
{
//save the position, index and cluster where the first free entry was found
//and copy the cluster to the buffer
startIndex = clusterIndex;
if (isRoot && device.getFatType() != FAT.FAT32)
{
startClusterNumber = clusterNumber;
System.arraycopy(cluster, 0, sectorBuffer, 0, cluster.length);
}
else
{
startClusterNumber = device.getFirstSectorNumberOfCluster(clusterNumber);
System.arraycopy(cluster, 0, clusterBuffer, 0, cluster.length);
}
List freeClusters = new LinkedList();
freeClusters.add(new Long(startClusterNumber));
do
{
counter++;
if (counter >= numOfFreeEntries)
{
if (isRoot && device.getFatType() != FAT.FAT32)
return new FreeInformation(startIndex, freeClusters, sectorBuffer, isRoot);
else
return new FreeInformation(startIndex, freeClusters, clusterBuffer, isRoot);
}
clusterIndex += 32;
if (clusterIndex < cluster.length)
System.arraycopy(cluster, clusterIndex, directoryEntry, 0, 32);
else //the end of actual cluster is reached
{
//get the next cluster
if (isRoot && device.getFatType() != FAT.FAT32)
{
if ((++clusterNumber) >= firstRootDirSecNum + numRootDirSectors)
{
//there is not enough space in the root directory
int numOfNeededClusters = MyMath.roundUp(((numOfFreeEntries - counter)*32.0f) / (bpb.SecPerClus*bpb.BytsPerSec));
throw new NotEnoughMemory(0, numOfNeededClusters);
}
cluster = readSector(clusterNumber); //root directory is only accessible by sector
freeClusters.add(new Long(clusterNumber));
}
else //try to get at least one cluster more of space
{
long fatContent = device.getFatContent(clusterNumber);
if (device.isLastCluster(fatContent))
{
long numOfNeededClusters = MyMath.roundUp(((numOfFreeEntries - counter)*32.0f) / (bpb.SecPerClus*bpb.BytsPerSec));
List moreFreeClusters = device.getFreeClusters(numOfNeededClusters, clusterNumber);
//clear the tempBuffer
byte[] tempBuffer = new byte[cluster.length];
for (int l=0; l < tempBuffer.length; l++)
tempBuffer[l] = 0;
//update the entries of the list
//they are indirect but we need direct ones for the FreeInformation object
for (int k=0; k < moreFreeClusters.size(); k++)
{
Long content = (Long)moreFreeClusters.get(k);
moreFreeClusters.set(
k,
new Long(device.getFirstSectorNumberOfCluster(content.longValue()))
);
//it's important that all of this new ordered cluster are initialized with
//zero values, otherwise the directory contains 'ghost entries'
//it's not the root directory of FAT12 or FAT16
writeCluster(
device.getFirstSectorNumberOfCluster(content.longValue()),
tempBuffer
);
}
freeClusters.addAll(moreFreeClusters);
return new FreeInformation(startIndex, freeClusters, clusterBuffer, isRoot);
} //end if device.isLastCluster(...)
clusterNumber = fatContent;
//it's not the root directory of FAT12 or FAT16
cluster = readCluster(device.getFirstSectorNumberOfCluster(fatContent));
freeClusters.add(new Long(device.getFirstSectorNumberOfCluster(clusterNumber)));
}
System.arraycopy(cluster, 0, directoryEntry, 0, 32);
clusterIndex = 0;
} //end else we need the next cluster
}
while(isFree(directoryEntry));
//if we reach this part, the operation could not be supported
//start a new search at the actual cluster
return getFreeDirectoryEntryIndex(
cluster,
clusterNumber,
clusterIndex, //we searched this cluster already until this index
numOfFreeEntries,
isRoot
);
} //end else: this cluster contains not enough (free) entries
} //end if(isFree(directoryEntry))
} //end for all entries of the cluster
if (isRoot && device.getFatType() != FAT.FAT32)
{
if ((++clusterNumber) >= firstRootDirSecNum + numRootDirSectors)
throw new NotEnoughMemory("There is no more space for an other entry in this directory");
return getFreeDirectoryEntryIndex(
readSector(clusterNumber),
clusterNumber,
0,
numOfFreeEntries,
isRoot
);
}
else
{
long fatContent = device.getFatContent(clusterNumber);
//if the fatContent points to the EOC_MARK then we reached the
//end of this directory, the only thing to do now is to order
//enough space to support this operation. In case there is not
//enough space left a NotEnoughMemoryException is thron.
if (device.isLastCluster(fatContent))
{
long numOfNeededClusters = MyMath.roundUp((numOfFreeEntries*32.0f) / (bpb.SecPerClus*bpb.BytsPerSec));
List moreFreeClusters = device.getFreeClusters(numOfNeededClusters, clusterNumber);
//clear the tempBuffer
byte[] tempBuffer = new byte[bpb.SecPerClus*bpb.BytsPerSec];
for (int l=0; l < tempBuffer.length; l++)
tempBuffer[l] = 0;
//update the entries of the list
//they are indirect but we need direct ones for the FreeInformation object
for (int k=0; k < moreFreeClusters.size(); k++)
{
Long content = (Long)moreFreeClusters.get(k);
moreFreeClusters.set(
k,
new Long(device.getFirstSectorNumberOfCluster(content.longValue()))
);
//it's important that all of this new ordered cluster are initialized with
//zero values, otherwise the directory contains 'ghost entries'
//it's not the root directory of FAT12 or FAT16
writeCluster(
device.getFirstSectorNumberOfCluster(content.longValue()),
tempBuffer
);
}
return new FreeInformation(0, moreFreeClusters, tempBuffer, isRoot);
}
return getFreeDirectoryEntryIndex(
//it's not the root directory of FAT12 or FAT16
readCluster(device.getFirstSectorNumberOfCluster(fatContent)),
fatContent,
0,
numOfFreeEntries,
isRoot
);
}
} //end getFreeDirectoryEntryIndex(byte[] cluster, long clusterNumber, int clusterIndex, int numOfFreeEntries, boolean isRoot)
/**
* Check if the given fileName is a directory or not. If the file name
* doesn't exist the method will return false.
* @param fileName the name of the directory
* @return true is the fileName is a directory otherwise return false.
*/
public boolean isDirectory(String fileName)
{
try
{
return isDirectory(getDirectoryEntry(fileName));
}
catch(FileDoesntExist e)
{
return false;
}
} //end isDirectory(fileName)
/**
* Check if the given fileName is a file or not. If the file name
* doesn't exist the method will return false.
* @param fileName the name of the file.
* @return true is the fileName is a file otherwise return false.
*/
public boolean isFile(String fileName)
{
try
{
return isFile(getDirectoryEntry(fileName));
}
catch(FileDoesntExist e)
{
return false;
}
} //end isFile(fileName)
/**
* Check if the given file name is in 8.3 naming convention.
* @param fileName the name of the file.
* @return true in case the file name is in 8.3 naming convention.
*/
public static boolean isShortName(String fileName)
{
//check if long name fits in a 8.3 naming convention
int dotIndex = fileName.lastIndexOf(".");
if (dotIndex >= 0)
{
String firstPart = fileName.substring(0, dotIndex);
String secondPart = fileName.substring(dotIndex + 1, fileName.length());
return ( !(containsIllicitValues(firstPart.getBytes())) &&
(firstPart.length() <= 8) &&
!(containsIllicitValues(secondPart.getBytes())) &&
(secondPart.length() <= 3)
);
}
else
{
return (!(containsIllicitValues(fileName.getBytes())) && (fileName.length() <= 8));
}
} //end isShortName(String fileName)
/**
* Return true if the byte array contains illegal values. That are
* all values smaller than 0x20 except 0x05 and also all values
* that are in the illicit-set.
* @param name the name.
* @return true if the byte array contains an illegal value otherwise
* false.
*/
public static boolean containsIllicitValues(byte[] name)
{
for (int i=0; i < name.length; i++)
{
if (name[i] < 0x20 && name[i] != 0x05)
return true;
if (illicitValues.contains(new Short(ByteArrayConversionsLittleEndian.convShort(name[i]))))
return true;
}
return false;
} //containsIllegalValues(byte[] name)
/**
* Return the recreated file name from a string stored as a directoryEntry.
* This method will always create a dot in the returned string so don't use
* this method with files that have the directory attribute set. The dot and
* dot dot strings will be returned without blanks at the end.
* @param buffer holds the information of the file name.
* @param from the start index inclusive.
* @param to the end index exclusive.
* @return the file name in 8.3 naming convention.
*/
public static String recreateFileName(byte[] buffer, int from, int to)
{
String str = ByteArrayConversionsLittleEndian.byteToString(buffer, from, to);
if (str.equals(". "))
return ".";
else if (str.equals(".. "))
return "..";
String firstPart = str.substring(0, 8);
String lastPart = str.substring(8, 11);
//clear all last blanks of both substrings
for (int i=0; i < 3; i++)
if (lastPart.endsWith(" "))
lastPart = lastPart.substring(0, lastPart.length() - 1);
else
break;
for (int i=0; i < 8; i++)
if (firstPart.endsWith(" "))
firstPart = firstPart.substring(0, firstPart.length() - 1);
else
break;
return firstPart + "." + lastPart;
} //end getFileName(byte[] buffer, int from, int to)
/**
* Return the directory entry which matches the given file name.
* @param fileName the name of the file.
* @return byte array representation of the directory entry.
* @throws FileDoesntExist exception in case the file doesn't exist.
*/
public byte[] getDirectoryEntry(String fileName) throws FileDoesntExist
{
DirectoryEntryInformation dirEntInf = findDirectoryEntry(fileName);
if (dirEntInf != null && dirEntInf.directoryEntry != null)
return dirEntInf.directoryEntry;
throw new FileDoesntExist(fileName);
} //end getDirectoryEntry(fileName)
/**
* Return the clusterNumber of the directory entry specified
* by fileName.
* @param fileName the name of the file.
* @return the cluster number.
* @throws FileDoesntExist exception in case the file doesn't exist.
*/
public long getClusterNumber(String fileName) throws FileDoesntExist
{
return getClusterNumber(getDirectoryEntry(fileName));
} //end getClusterNumber(String fileName)
/**
* Update the cluster number of the file given by directory name with the given
* cluster number. In case the file doesn't exist a FileDoesntExist exception
* will be thrown.
* @param directoryName the name of the file.
* @param clusterNumber the new cluster number.
* @throws DirectoryException in case of a directory error.
*/
protected void setClusterNumber(String directoryName, long clusterNumber) throws DirectoryException
{
DirectoryEntryInformation dirEntInf = findDirectoryEntry(directoryName);
if (dirEntInf == null || dirEntInf.directoryEntry == null)
throw new FileDoesntExist(directoryName);
setClusterNumber(dirEntInf.directoryEntry, clusterNumber);
writeDirectoryEntry(dirEntInf);
} //end setClusterNumber(String directoryName, long clusterNumber)
/**
* Return the length of the file given by file name. If the file doesn't exist
* 0 is returned.
* @param fileName the name of the file.
* @return the length of the file, if it doesn't exist 0 is returned.
*/
public long length(String fileName)// throws FileDoesntExist
{
try
{
return getFileLength(getDirectoryEntry(fileName));
}
catch(Exception e)
{
return 0;
}
} //end getSize(String fileName)
/**
* Return the last write time stored at the directory entry given
* by the fileName. The returned value is the number of milliseconds
* since January 1, 1970, 00:00:00 GMT.
* @param fileName the name of the file.
* @return the number of milliseconds since January 1, 1970, 00:00:00 GMT
* until the last write time.
*/
public long getLastWriteTime(String fileName)
{
try
{
byte[] directoryEntry = getDirectoryEntry(fileName);
DirectoryTime time = getWriteTime(directoryEntry);
DirectoryDate date = getWriteDate(directoryEntry);
Calendar temp = new GregorianCalendar(
date.year,
date.month - 1,
date.day,
time.hour,
time.minute,
time.second
);
return temp.getTime().getTime(); //access to Date first and from there to getTime() to return the long value
}
catch (FileDoesntExist e)
{
return 0;
}
} //end getLastWriteTime(String fileName)
/**
* Return the creation time stored at the directory entry given
* by the fileName. The returned value is the number of milliseconds
* since January 1, 1970, 00:00:00 GMT.
* @param fileName the name of the file.
* @return the number of milliseconds since January 1, 1970, 00:00:00 GMT,
* until the creation time.
*/
public long getCreationTime(String fileName)
{
try
{
byte[] directoryEntry = getDirectoryEntry(fileName);
DirectoryTime time = getCreationTime(directoryEntry);
DirectoryDate date = getCreationDate(directoryEntry);
Calendar temp = new GregorianCalendar(
date.year,
date.month - 1,
date.day,
time.hour,
time.minute,
time.second
);
return temp.getTime().getTime(); //access to Date first and from there to getTime() to return the long value
}
catch (FileDoesntExist e)
{
return 0;
}
} //end getCreationTime(String fileName)
/**
* Returns the attribute stored at the directory entry given by fileName.
* @param fileName the name of the file.
* @return the attribute of the directory entry given by fileName.
*/
public byte getAttribute(String fileName)
{
try
{
byte[] directoryEntry = getDirectoryEntry(fileName);
return getAttribute(directoryEntry);
}
catch (FileDoesntExist e)
{
return 0;
}
}
/**
* Renames the file denoted by directoryName.
* @param directoryName the name of the file to rename
* @param newName the new name of the file.
* @return true if and only if the renaming succeeded; false otherwise.
*/
protected boolean renameTo(String directoryName, String newName)
{
DirectoryEntryInformation dirEntInf1 = null;
try
{
String path = StringOperations.extractPath(directoryName);
//check if the path exists
ClusterTuple clusterTuple = getCluster(path);
if (clusterTuple == null)
return false;
String fileName = StringOperations.extractFileName(directoryName);
//extract path and name of newName
String path2 = StringOperations.extractPath(newName);
String newFileName = StringOperations.extractFileName(newName);
//get directory entry of the file directoryName
dirEntInf1 = findDirectoryEntry(
clusterTuple.clusterNumber,
fileName,
clusterTuple.isRoot
);
if (dirEntInf1 == null || dirEntInf1.directoryEntry == null)
return false;
//create the directory structure to newName in case it doesn't exist.
StringTokenizer stringTokenizer = new StringTokenizer(path2, System.getProperty("file.separator"));
String tempPath = "";
while (stringTokenizer.hasMoreTokens())
{
tempPath += stringTokenizer.nextToken() + System.getProperty("file.separator");
if (!exists(tempPath))
createDirectory(tempPath);
}
//remove directoryName must be done before the creation of the new entry
//otherwise it might be that the new entry will be deleted also.
if (!isDirectory(dirEntInf1.directoryEntry) ||
//the content of a directory is never stored at the root directory
//except for the root directory itself, but this directory is not
//deletable
(isDirectory(dirEntInf1.directoryEntry) &&
directoryIsEmpty(getClusterNumber(dirEntInf1.directoryEntry), false)))
{
//mark the directory entry as free
setFree(dirEntInf1.directoryEntry);
writeDirectoryEntry(dirEntInf1);
}
//get the clusterTuple for the destination file
ClusterTuple clusterTuple2 = getCluster(path2);
//create a new directory structure entry
DirectoryTime creationTime = getCreationTime(dirEntInf1.directoryEntry);
DirectoryDate creationDate = getCreationDate(dirEntInf1.directoryEntry);
short creationTimeTenth = getCreationTimeTenth(dirEntInf1.directoryEntry);
//if the old entry was a directory we have to create new dot and dot dot entries.
if (isDirectory(dirEntInf1.directoryEntry))
{
createDirectory(
clusterTuple2,
newName,
ATTR_DIRECTORY,
creationTime.hour,
creationTime.minute,
creationTime.second,
creationTimeTenth,
creationDate.day,
creationDate.month,
creationDate.year
);
}
else
{
DirectoryStructure dirEnt = new DirectoryStructure(
newFileName,
getAttribute(dirEntInf1.directoryEntry),
creationTime.hour,
creationTime.minute,
creationTime.second,
getCreationTimeTenth(dirEntInf1.directoryEntry),
creationDate.day,
creationDate.month,
creationDate.year,
getClusterNumber(dirEntInf1.directoryEntry), //the cluster number stored at the new entry
getFileLength(dirEntInf1.directoryEntry),
clusterTuple2.clusterNumber, //the cluster number where the new entry should be stored
clusterTuple2.isRoot
);
//order space to store the new entry
FreeInformation freeInformation = getFreeDirectoryEntryIndex(
clusterTuple2.clusterNumber,
dirEnt.getNumOfEntries(),
clusterTuple2.isRoot
);
//write the new entry to disk
writeDirectoryEntry(freeInformation, dirEnt.getByte());
}
}
catch(FileDoesntExist e)
{
return false;
}
catch(NotEnoughMemory e)
{
try
{
if (dirEntInf1 != null)
writeDirectoryEntry(dirEntInf1);
}
catch (Exception exe)
{
}
return false;
}
catch(IllegalName e)
{
return false;
}
catch(DirectoryException e)
{
try
{
if (dirEntInf1 != null)
writeDirectoryEntry(dirEntInf1);
}
catch (Exception exe)
{
}
return false;
}
return true;
} //end renameTo(...)
/**
* Deletes the file or directory denoted by the given filename. If
* this pathname denotes a directory, then the directory must be empty in
* order to be deleted.
*
* @param fileName the given filename
* @return true if and only if the file or directory is successfully
* deleted; false otherwise.
*/
public boolean delete(String fileName)
{
return delete(findDirectoryEntry(fileName));
} //end delete(String fileName)
/**
* Deletes the file or directory denoted by dirEntInf. If dirEntInf
* denotes a directory, then the directory must be empty in
* order to be deleted.
*
* @param dirEntInf an information denoting the file or directory to be
* deleted.
* @return true if and only if the file or directory is successfully
* deleted; false otherwise.
*/
private boolean delete(DirectoryEntryInformation dirEntInf)
{
try
{
if (dirEntInf == null || dirEntInf.directoryEntry == null)
{
return false;
}
if (isDirectory(dirEntInf.directoryEntry) &&
//the content of a directory is never stored at the root directory
//except for the root directory itself, but this directory is not
//deletable
!directoryIsEmpty(getClusterNumber(dirEntInf.directoryEntry), false))
{
return false;
}
//mark the directory entry as free
setFree(dirEntInf.directoryEntry);
writeDirectoryEntry(dirEntInf);
//mark all FAT entries that are used by the directory or file as free
long clusterNumber = getClusterNumber(dirEntInf.directoryEntry);
if (clusterNumber >= 2)
device.addFreeClusters(clusterNumber);
return true;
}
catch(DirectoryException e)
{
return false;
}
} //end delete(String fileName)
/**
* Return true if the file associated with the given file name is hidden.
* If no such file exists false is returned.
* @param fileName the name of the file.
* @return true if the file is marked as hidden.
*/
public boolean isHidden(String fileName)
{
try
{
return isHidden(getDirectoryEntry(fileName));
}
catch(FileDoesntExist e)
{
return false;
}
} //end isHidden(fileName)
/**
* Return true if the file associated with the given file name is read only.
* If no such file exists true is returned.
* @param fileName the name of the file.
* @return true if the file is marked as read only or if the file doesn't exist.
*/
public boolean isReadOnly(String fileName)
{
try
{
return isReadOnly(getDirectoryEntry(fileName));
}
catch(FileDoesntExist e)
{
return true;
}
} //end isReadOnly(fileName)
/**
* Set the last write time of the directory entry given by
* fileName.
* @param fileName the name of the file.
* @param time is the last write time in milliseconds since
* January 1, 1970, 00:00:00 GMT.
* @return true if the last write time could be set otherwise false.
*/
protected boolean setLastWriteTime(String fileName, long time)
{
try
{
DirectoryEntryInformation dirEntInf = findDirectoryEntry(fileName);
if (dirEntInf == null || dirEntInf.directoryEntry == null)
return false;
setWriteTime(dirEntInf.directoryEntry, time);
writeDirectoryEntry(dirEntInf);
return true;
}
catch(DirectoryException e)
{
return false;
}
} //end setLastWriteTime(String fileName, long time)
/**
* Set the last write date of the directory entry given by fileName.
* @param fileName the name of the file.
* @param year the year of the last write date.
* @param month the month of the last write date.
* @param day the day of the last write date.
* @return true if the last date could be set; false otherwise.
*/
protected boolean setLastWriteDate(String fileName, int year, int month, int day)
{
try
{
DirectoryEntryInformation dirEntInf = findDirectoryEntry(fileName);
if (dirEntInf == null || dirEntInf.directoryEntry == null)
return false;
setLastWriteDate(dirEntInf.directoryEntry, year, month, day);
writeDirectoryEntry(dirEntInf);
return true;
}
catch(DirectoryException e)
{
return false;
}
} //end setLastWriteDate(String fileName, int year, int month, int day)
/**
* Set the last access date of the directory entry given by fileName.
* @param fileName the name of the file.
* @param year the year of the last write date.
* @param month the month of the last write date.
* @param day the day of the last write date.
* @return true if the last access date could be set; false otherwise.
*/
protected boolean setLastAccessDate(String fileName, int year, int month, int day)
{
try
{
DirectoryEntryInformation dirEntInf = findDirectoryEntry(fileName);
if (dirEntInf == null || dirEntInf.directoryEntry == null)
return false;
setLastAccessDate(dirEntInf.directoryEntry, year, month, day);
writeDirectoryEntry(dirEntInf);
return true;
}
catch(DirectoryException e)
{
return false;
}
} //end setLastAccessDate(String fileName, int year, int month, int day)
/**
* Update the length of the file associated by the given file name.
* @param fileName the name of the file.
* @param fileLength the new length.
* @return true in case the length could be set; false otherwise.
*/
protected boolean writeLength(String fileName, long fileLength)// throws DirectoryException
{
try
{
DirectoryEntryInformation dirEntInf = findDirectoryEntry(fileName);
if (dirEntInf == null || dirEntInf.directoryEntry == null)
return false;
setFileLength(dirEntInf.directoryEntry, fileLength);
writeDirectoryEntry(dirEntInf);
return true;
}
catch(DirectoryException e)
{
return false;
}
} //end writeLength(String fileName, long fileLength)
/**
* Returns an array of strings naming the files and directories in the
* directory denoted by this fileName.
*
* If this fileName does not denote a directory, then this
* method returns null. Otherwise an array of strings is
* returned, one for each file or directory in the directory. Names
* denoting the directory itself and the directory's parent directory are
* not included in the result. Each string is a file name rather than a
* complete path.
*
* There is no guarantee that the name strings in the resulting array
* will appear in any specific order; they are not, in particular,
* guaranteed to appear in alphabetical order.
*
* @param fileName the given filename
* @return An array of strings naming the files and directories in the
* directory denoted by this fileName. The array will be
* empty if the directory is empty. Returns null if
* this fileName does not denote a directory, or if an
* I/O error occurs.
*
*/
public String[] list(String fileName)
{
try
{
if (fileName.equals(System.getProperty("file.separator")) || fileName.equals(""))
return listRoots();
byte[] directoryEntry = getDirectoryEntry(fileName);
if (!isDirectory(directoryEntry))
return null;
long clusterNumber = getClusterNumber(directoryEntry);
ArrayList result = new ArrayList();
Iterator entriesIterator = new EntriesFilterIterator(
new ListClusterChainEntriesIterator(device,
clusterNumber,
false //it can not be the root directory
),
LIST_DIRECTORY_ENTRY | LIST_FILE_ENTRY
);
while(entriesIterator.hasNext())
{
DirectoryEntryInformation dirEntInf = (DirectoryEntryInformation)entriesIterator.next();
result.add(getName(dirEntInf.directoryEntry));
}
return (String[])(result.toArray(new String[0]));
}
catch(FileDoesntExist e)
{
return null;
}
} //end list(String fileName)
/**
* List the files of the root directory.
* @return an array of strings denoting the files and directories of the root
* directory or an empty array in case there is no entry in the root directory.
*/
public String[] listRoots()
{
ArrayList result = new ArrayList();
Iterator entriesIterator = new EntriesFilterIterator(
new ListClusterChainEntriesIterator(
device,
firstRootDirSecNum,
true
),
LIST_DIRECTORY_ENTRY | LIST_FILE_ENTRY
);
while(entriesIterator.hasNext())
{
DirectoryEntryInformation dirEntInf = (DirectoryEntryInformation)entriesIterator.next();
result.add(getName(dirEntInf.directoryEntry));
}
return (String[])(result.toArray(new String[0]));
} //end listRoots()
/**
* Return a short name made of the given long name for the directory
* that is associated with the given cluster number. In case there are
* more than one short names in a directory that equals each other they
* get a numeric tail in order to distinguish them. Of course this
* is only possible if the long names for this short names are not equal
* to each other. In case the short name is unique for this directory and
* the longname fits the 8.3 naming convention (except the upper case rule)
* there will be no numeric tail generated.
* @param longName the long name.
* @param clusterIterator iterator that iterates over the cluster chain where
* the the given longName is stored.
* @return the unique short name of the given long name for the directory given
* by clusterIterator.
*/
protected String numericTailGeneration(String longName, Iterator clusterIterator)
{
String basisName = getBasisName(longName);
//copy the cluster chain in one byte array
LinkedList clusterList = new LinkedList();
while(clusterIterator.hasNext())
clusterList.addLast(clusterIterator.next());
if (clusterList.size() < 1)
return basisName; //cluster list is empty -> no collision between names
int clusterLength = ((byte[])clusterList.get(0)).length;
byte[] cluster = new byte[clusterLength*clusterList.size()];
for (int i=0; i < clusterList.size(); i++)
{
System.arraycopy(
(clusterList.get(i)),
0,
cluster,
i*clusterLength,
clusterLength
);
}
//search basis-name in cluster and find a free numeric tail
//in case the basis-name exist
BitSet usedNumbers = new BitSet(20);
byte[] directoryEntry = new byte[32]; //findDirectoryEntry(cluster, basisName);
String nameTmp;
boolean nameExist = false;
for (int i=0; i < cluster.length; i += 32)
{
System.arraycopy(cluster, i, directoryEntry, 0, 32);
nameTmp = getName(directoryEntry);
if (nameTmp.equals(basisName) && !isLongEntry(directoryEntry))
{
usedNumbers.set((int)StringOperations.extractNumericTail(nameTmp));
nameExist = true;
}
}
int freeNumber = 1;
int numberOfDigits = 1;
if (!nameExist)
{
if (isShortName(longName))
return longName; //the short name is only the basis-name without the numeric tail
}
else //allright there is an other entry with the same short name
{
//search the usedNumbers for a free number
for (; freeNumber < usedNumbers.size(); freeNumber++)
if (!usedNumbers.get(freeNumber))
break; //we found a free number
//calculate how many digits freeNumber has
while(freeNumber % (10*numberOfDigits) != freeNumber)
numberOfDigits++;
}
//insert the number in the basis name so that the naming convention holds
//split basis-name in two strings
int dotIndex = basisName.indexOf(".", 0);
String firstPart = "";
String fileExtension = "";
if (dotIndex != -1)
{
firstPart = basisName.substring(0, dotIndex);
fileExtension = basisName.substring(dotIndex, basisName.length()); //inclusive dot
}
else
firstPart = basisName.substring(0);
if (8 - numberOfDigits - 1 > firstPart.length())
return firstPart + "~" + new Long(freeNumber).toString() + fileExtension;
else
return firstPart.substring(0, 8-numberOfDigits-1) + "~" + new Long(freeNumber).toString() + fileExtension;
} //end numericTailGeneration(String longName, Iterator clusterIterator)
/**
* Create the short name (8.3 name) from a given long name.
* @param longName the name of a file or directory for which the
* 8.3 naming convention doesn't hold.
* @return the basis name of the given longName.
*/
public static String getBasisName(String longName)
{
//UNICODE name is converted to upper case
//longName.toUpperCase();
longName = longName.toUpperCase();
//normally at this point the long name has to be converted
//from UNICODE to OEM, but this can not be done, since
//then all the other operations will go wrong.
//Therefore we do this step at the end.
//strip all leading and embedded spaces from the long name.
//search the last character that is not a space.
int i = longName.length()-1;
for (; i >= 0 ; i--)
if (!longName.substring(i, i+1).equals(" "))
break;
String firstPart = longName.substring(0, i+1);
String secondPart = longName.substring(i+1, longName.length());
//remove all spaces from firstPart
StringTokenizer stringTokenizer = new StringTokenizer(firstPart, " ");
firstPart = "";
while (stringTokenizer.hasMoreTokens())
{
firstPart += stringTokenizer.nextToken();
}
longName = firstPart+secondPart; //concatenate the strings
//strip all leading periods (.) from the long name
int lastIndex = longName.lastIndexOf(".");
if (lastIndex != -1)
{
firstPart = longName.substring(0, lastIndex);
secondPart = longName.substring(lastIndex, longName.length());
//remove all periods in the firstPart
longName = "";
stringTokenizer = new StringTokenizer(firstPart, ".");
while (stringTokenizer.hasMoreTokens())
{
longName += stringTokenizer.nextToken();
}
longName += secondPart;
}
//copy the first eight characters to the result
String basisName = "";
for (i=0; i < 8 && i < longName.length(); i++)
{
if (longName.substring(i, i+1).equals("."))
break;
basisName += longName.substring(i, i+1);
}
//insert a dot at the end of the primary components of the basis-name
//iff the basis (long?) name has an extension after the last period
//in the name
if (longName.lastIndexOf(".") != -1 && !longName.endsWith("."))
{
basisName += ".";
//copy the extension from the long name to the basis name
int start = longName.lastIndexOf(".") + 1;
for (i=start; i < longName.length() && i < start + 3; i++)
{
basisName += longName.substring(i, i+1);
}
}
int index = basisName.lastIndexOf(".");
firstPart = basisName.substring(0, (index != -1) ? index : basisName.length());
String lastPart = "";
if (index != -1)
{
lastPart = basisName.substring(index + 1, basisName.length());
basisName = convertToOEM(firstPart) + "." + convertToOEM(lastPart);
}
else
basisName = convertToOEM(firstPart);
return basisName.toUpperCase();
} //end getBasisName(String longName)
/**
* Converts the given string to OEM. All characters that are
* not allowed to be in a short name are replaced by an
* underscore '_'.
* @param str the String to convert.
* @return the converted String.
*/
public static String convertToOEM(String str)
{
//convert UNICODE to OEM
String result = "";
byte[] tempArr = str.getBytes();
short[] tempName = new short[tempArr.length];
for (int i=0; i < tempArr.length; i++)
{
char c = str.charAt(i);
tempName[i] = ByteArrayConversionsLittleEndian.convShort(tempArr[i]);
if ((tempName[i] < 0x20 && tempName[i] != 0x05) ||
DIR.illicitValues.contains( new Short(tempName[i]) ))
c = '_';
result += c;
}
return result;
} //end convertToOEM(String str)
/**
* Returns the checksum of the given name. The checksum is stored in long directory
* entries. The name must have a length of 11 and it must be in the format of a
* MS-DOS directory entry. See the paper: "Hardware White Paper. Microsoft Extensible
* Firmware Initiative FAT32 File System specification. Version 1.03 DEcember 6, 2000"
* for more details.
* @param name assumed to be 11 bytes long.
* @return the checksum.
*/
public static short checksum(byte[] name)
{
//even the checksum is only a byte the calculation must be
//with short otherwise the result is wrong but the calculation
//doesn't use more than 8 bit to store the temporary result.
short sum = 0;
short tmp;
for (int i=0; i < 11; i++)
{
if ((sum & 1) == 1)
tmp = 0x80;
else
tmp = 0;
sum = (short)((tmp + (sum >> 1) + name[i]) & 0xFF);
}
return sum;
} //end checksum(byte[] name)
/**
* Check if the directory given by clusterNumber is empty.
* @param clusterNumber the number of the cluster.
* @param isRoot indicates if the clusterNumber belongs to the root directory.
* @return return true if the directory is empty; false otherwise.
*/
protected boolean directoryIsEmpty(long clusterNumber, boolean isRoot)
{
Iterator entriesIterator = new EntriesFilterIterator(
new ListClusterChainEntriesIterator(
device,
clusterNumber,
isRoot
),
LIST_ALL_EXCEPT_FREE
);
int counter = 0;
while (entriesIterator.hasNext())
{
//DirectoryEntryInformation dirEntInf = (DirectoryEntryInformation)entriesIterator.next();
entriesIterator.next();
//you might want to check if the entries are something else than dot and dot dot
counter++;
}
return counter == 2;
}
////////////////////////////////////
// //
// byte[] directoryEntry methods //
// //
////////////////////////////////////
/**
* Set the cluster number of the given directory entry to the given cluster number.
* The method will only update the directory entry, but will not
* save the information in the directory structure.
* @param directoryEntry the given directory entry.
* @param clusterNumber the new cluster number.
*/
public static void setClusterNumber(byte[] directoryEntry, long clusterNumber)
{
//firstClusterHI
directoryEntry[directoryEntry.length-32+20] = (byte)(clusterNumber >> 16);
directoryEntry[directoryEntry.length-32+21] = (byte)(clusterNumber >> 24);
//firstClusterLO
directoryEntry[directoryEntry.length-32+26] = (byte)(clusterNumber);
directoryEntry[directoryEntry.length-32+27] = (byte)(clusterNumber >> 8);
} //end setClusterNumber(byte[] directoryEntry, long clusterNumber)
/**
* Return the cluster number of the given directoryEntry.
* @param directoryEntry the given directory entry.
* @return the cluster number stored in directoryEntry.
*/
public static long getClusterNumber(byte[] directoryEntry)
{
long firstClusterHI = ByteArrayConversionsLittleEndian.convInt(
directoryEntry[directoryEntry.length-32+20],
directoryEntry[directoryEntry.length-32+21]
);
long firstClusterLO = ByteArrayConversionsLittleEndian.convInt(
directoryEntry[directoryEntry.length-32+26],
directoryEntry[directoryEntry.length-32+27]
);
return firstClusterLO | (firstClusterHI << 16);
} //end getClusterNumber(directoryEntry)
/**
* Return the creation time of the given directory entry.
* @param directoryEntry the given directory entry.
* @return DirectoryTime contains the hour, minute, and second the entry was made.
*/
public static DirectoryTime getCreationTime(byte[] directoryEntry)
{
short index14 = (short)(directoryEntry.length - 32 + 14);
short index15 = (short)(directoryEntry.length - 32 + 15);
byte hour = (byte)(ByteArrayConversionsLittleEndian.convShort(directoryEntry[index15]) >> 3);
int tmp = ByteArrayConversionsLittleEndian.convInt(directoryEntry[index14], directoryEntry[index15]);
byte minute = (byte)((tmp >> 5) & 0x003F);
byte second = (byte)((directoryEntry[index14] & 0x1F) << 1);
return new DirectoryTime((byte)(hour + 1), minute, second);
} //end getCreationTime(byte[] directoryEntry)
/**
* Return the creation time tenth (of a second) of the given directory entry.
* @param directoryEntry the given directory entry.
* @return the tenth of the second as the entry was made.
*/
public static short getCreationTimeTenth(byte[] directoryEntry)
{
//index 13
return (short)((ByteArrayConversionsLittleEndian.convShort(directoryEntry[directoryEntry.length - 32 + 13]) * 10) / 2);
} //end getCreationTimeTenth(byte[] directoryEntry)
/**
* Return the last write time of the given directory entry.
* @param directoryEntry the given directory entry.
* @return DirectoryTime contains the hour, minute, and second of the last write access.
*/
public static DirectoryTime getWriteTime(byte[] directoryEntry)
{
short index22 = (short)(directoryEntry.length - 32 + 22);
short index23 = (short)(directoryEntry.length - 32 + 23);
byte hour = (byte)(ByteArrayConversionsLittleEndian.convShort(directoryEntry[index23]) >> 3);
int tmp = ByteArrayConversionsLittleEndian.convInt(directoryEntry[index22], directoryEntry[index23]);
byte minute = (byte)((tmp >> 5) & 0x003F);
byte second = (byte)((directoryEntry[index22] & 0x1F) << 1);
return new DirectoryTime((byte)(hour + 1), minute, second);
} //end getCreationTime(byte[] directoryEntry)
/**
* Sets the last-modified time of the given directoryEntry.
* @param directoryEntry the given directory entry.
* @param time the new last-modified time, measured in milliseconds since
* the epoch (00:00:00 GMT, January 1, 1970).
*/
public static void setWriteTime(byte[] directoryEntry, long time)
{
GregorianCalendar writeTime = new GregorianCalendar();
writeTime.setTime(new Date(time));
int writeTimeTemp = 0;
writeTimeTemp = writeTime.get(Calendar.SECOND) >> 1;
writeTimeTemp |= ((writeTime.get(Calendar.MINUTE)) << 5);
writeTimeTemp |= ((writeTime.get(Calendar.HOUR_OF_DAY) - 1) << 11);
directoryEntry[directoryEntry.length - 32 + 22] = (byte)writeTimeTemp;
directoryEntry[directoryEntry.length - 32 + 23] = (byte)(writeTimeTemp >> 8);
} //end setWriteTime(byte[] directoryEntry, long time)
/**
* Sets the last write date to the given directory entry.
* @param directoryEntry the given directory entry.
* @param year the last write year, valid range from 1980 to 2107.
* @param month the last write month, valid range from 1 to 12.
* @param day the last write day, valid range from 1 to 31.
*/
public static void setLastWriteDate(byte[] directoryEntry, int year, int month, int day)
{
year -= 1980;
int writeDate = 0;
writeDate = day;
writeDate |= (month << 5);
writeDate |= (year << 9);
directoryEntry[directoryEntry.length - 32 + 24] = (byte)writeDate;
directoryEntry[directoryEntry.length - 32 + 25] = (byte)(writeDate >> 8);
} //end setLastWriteDate(byte[] directoryEntry, int year, int month, int day)
/**
* Sets the last access date to the given directory entry.
* @param directoryEntry the given directory entry.
* @param year the last access year, valid range from 1980 to 2107.
* @param month the last access month, valid range from 1 to 12.
* @param day the last access day, valid range from 1 to 31.
*/
public static void setLastAccessDate(byte[] directoryEntry, int year, int month, int day)
{
year -= 1980;
int accessDate = 0;
accessDate = day;
accessDate |= (month << 5);
accessDate |= (year << 9);
directoryEntry[directoryEntry.length - 32 + 18] = (byte)accessDate;
directoryEntry[directoryEntry.length - 32 + 19] = (byte)(accessDate >> 8);
} //end setLastAccessDate(byte[] directoryEntry, int year, int month, int day)
/**
* Returns the creation date of the given directory entry.
* @param directoryEntry the given directory entry.
* @return DirectoryDate contains the day, month, and year this entry was made.
*/
public static DirectoryDate getCreationDate(byte[] directoryEntry)
{
short index16 = (short)(directoryEntry.length - 32 + 16);
short index17 = (short)(directoryEntry.length - 32 + 17);
byte year = (byte)(ByteArrayConversionsLittleEndian.convShort(directoryEntry[index17]) >> 1);
int tmp = ByteArrayConversionsLittleEndian.convInt(directoryEntry[index16], directoryEntry[index17]);
byte month = (byte)((tmp >> 5) & 0x000F);
byte day = (byte)(directoryEntry[index16] & 0x1F);
return new DirectoryDate(year + 1980, month, day);
} //end getCreationDate(byte[] directoryEntry)
/**
* Returns the last access date of the given directory entry.
* @param directoryEntry the given directory entry.
* @return DirectoryDate contains the day, month, and year of the last access.
*/
public static DirectoryDate getLastAccessDate(byte[] directoryEntry)
{
short index18 = (short)(directoryEntry.length - 32 + 18);
short index19 = (short)(directoryEntry.length - 32 + 19);
byte year = (byte)(ByteArrayConversionsLittleEndian.convShort(directoryEntry[index19]) >> 1);
int tmp = ByteArrayConversionsLittleEndian.convInt(directoryEntry[index18], directoryEntry[index19]);
byte month = (byte)((tmp >> 5) & 0x000F);
byte day = (byte)(directoryEntry[index18] & 0x1F);
return new DirectoryDate(year + 1980, month, day);
} //end getLastAccessDate(byte[] directoryEntry)
/**
* Returns the date of the last write of the given directory entry.
* @param directoryEntry the given directory entry.
* @return DirectoryDate contains the day the month and the year of the last write.
*/
public static DirectoryDate getWriteDate(byte[] directoryEntry)
{
short index25 = (short)(directoryEntry.length - 32 + 25);
short index24 = (short)(directoryEntry.length - 32 + 24);
byte year = (byte)(ByteArrayConversionsLittleEndian.convShort(directoryEntry[index25]) >> 1);
int tmp = ByteArrayConversionsLittleEndian.convInt(directoryEntry[index24], directoryEntry[index25]);
byte month = (byte)((tmp >> 5) & 0x000F);
byte day = (byte)(directoryEntry[index24] & 0x1F);
return new DirectoryDate(year + 1980, month, day);
} //end getWriteDate(byte[] directoryEntry)
/**
* Returns true if the given directoryEntry is marked as hidden.
* @param directoryEntry the given directory entry.
* @return true if the directory entry is marked as hidden; false otherwise.
*/
public static boolean isHidden(byte[] directoryEntry)
{
//index 11
return (directoryEntry[directoryEntry.length - 32 + 11] & ATTR_HIDDEN) == ATTR_HIDDEN;
} //end isHidden(byte[] directoryEntry)
/**
* Returns true if the given directoryEntry is marked as read only.
* @param directoryEntry the given directory entry.
* @return true if the directory entry is marked as read only; false otherwise.
*/
public static boolean isReadOnly(byte[] directoryEntry)
{
//index 11
return (directoryEntry[directoryEntry.length - 32 + 11] & ATTR_READ_ONLY) == ATTR_READ_ONLY;
} //end isReadOnly(byte[] directoryEntry)
/**
* Checks if the given directory entry is marked as a directory.
* @param directoryEntry the given directory entry.
* @return true if the directory entry is a directory.
*/
public static boolean isDirectory(byte[] directoryEntry)
{
if (((directoryEntry[directoryEntry.length - 32 + 11] & ATTR_LONG_NAME_MASK) != ATTR_LONG_NAME) &&
(directoryEntry[directoryEntry.length - 32 + 0] != FREE_ENTRY))
return (directoryEntry[directoryEntry.length - 32 + 11] & (ATTR_DIRECTORY | ATTR_VOLUME_ID)) == ATTR_DIRECTORY;
return false;
}
/**
* Checks if the given directory entry is the dot entry.
* @param directoryEntry the given directory entry.
* @return true if the directory entry is a dot entry.
*/
public static boolean isDotEntry(byte[] directoryEntry)
{
String str = ByteArrayConversionsLittleEndian.byteToString(
directoryEntry,
directoryEntry.length - 32,
directoryEntry.length - 32 + 11
);
return str.equals(". ") && isDirectory(directoryEntry);
} //end isDotEntry(byte[] directoryEntry)
/**
* Checks if the given directory entry is the dot dot entry.
* @param directoryEntry the given directory entry.
* @return true if the directory entry is the dot dot entry.
*/
public static boolean isDotDotEntry(byte[] directoryEntry)
{
String str = ByteArrayConversionsLittleEndian.byteToString(
directoryEntry,
directoryEntry.length - 32,
directoryEntry.length - 32 + 11
);
return str.equals(".. ") && isDirectory(directoryEntry);
} //end isDotDotEntry(byte[] directoryEntry)
/**
* Checks if the given directory entry is a file entry.
* @param directoryEntry the given directory entry.
* @return true if the directoryEntry is marked as a file.
*/
public static boolean isFile(byte[] directoryEntry)
{
if (((directoryEntry[directoryEntry.length - 32 + 11] & ATTR_LONG_NAME_MASK) != ATTR_LONG_NAME) &&
(directoryEntry[directoryEntry.length - 32 + 0] != FREE_ENTRY))
return (directoryEntry[directoryEntry.length - 32 + 11] & (ATTR_DIRECTORY | ATTR_VOLUME_ID)) == 0x00;
return false;
} //end isFile(byte[] directoryEntry)
/**
* Checks if the given directory entry is the root directory entry.
* @param directoryEntry the given directory entry.
* @return true if the directory entry is the root directory entry.
*/
public static boolean isRootDirectory(byte[] directoryEntry)
{
if (((directoryEntry[directoryEntry.length - 32 + 11] & ATTR_LONG_NAME_MASK) != ATTR_LONG_NAME) &&
(directoryEntry[directoryEntry.length - 32 + 0] != FREE_ENTRY))
return (directoryEntry[directoryEntry.length - 32 + 11] & (ATTR_DIRECTORY | ATTR_VOLUME_ID)) == ATTR_VOLUME_ID;
return false;
} //end isRootDirectory(byte[] directoryEntry)
/**
* Returns true if the given directory entry is a part of the set
* of entries for a long directory entry. To be true, the attribute
* ATTR_LONG_NAME must be set.
* @param directoryEntry the given directory entry.
* @return true in case the directory entry is a long entry.
*/
public static boolean isLongEntry(byte[] directoryEntry)
{
return (((directoryEntry[11] & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) && (directoryEntry[0] != FREE_ENTRY));
} //end isLongEntry(byte[] directoryEntry)
/**
* Checks if the given directory entry is the last long entry of the set
* of directory entries for one file.
* Use this method only on long directory entries.
* @param directoryEntry the given directory entry.
* @return true if the directory entry is the last entry of the set
* of the directory entries for one file.
*/
public static boolean isLastLongEntry(byte[] directoryEntry)
{
//index 11
return ((directoryEntry[directoryEntry.length - 32 + 11] & 0x40) == 0x40);
} //end isLastLongEntry(byte[] directoryEntry)
/**
* Returns the order number of the given directory entry. The last
* order value flag is not returned. Use this method only on
* long directory entries.
* @param directoryEntry the given directory entry.
* @return the order number.
*/
public static short getOrderNumber(byte[] directoryEntry)
{
return ByteArrayConversionsLittleEndian.convShort((byte)(directoryEntry[0] & 0xBF));
} //end getOrderNumber(byte[] directoryEntry)
/**
* Checks if the given directory entry is marked as free entry.
* @param directoryEntry the given directory entry.
* @return true if the given directory entry is marked as free.
*/
public static boolean isFree(byte[] directoryEntry)
{
byte value = directoryEntry[0];
return (value == FREE_ENTRY ||
value == FREE_ENTRY_AND_NEXTS_FREE ||
value == FREE_ENTRY_KANJI);
} //end isFree(byte[] directoryEntry)
/**
* Checks if the given directory entry is the last entry. If a directory entry
* is marked as last entry the file system can use this information to accelerate
* the access.
* @param directoryEntry the given directory entry.
* @return true if the given directory entry is marked as last entry.
*/
public static boolean isLastEntry(byte[] directoryEntry)
{
return ByteArrayConversionsLittleEndian.convShort(directoryEntry[0]) == FREE_ENTRY_AND_NEXTS_FREE;
} //end isLastEntry(byte[] directoryEntry)
/**
* Returns the name contained in the given directoryEntry.
* @param directoryEntry the given directory entry.
* @return the name.
*/
public static String getName(byte[] directoryEntry)
{
if (isLongEntry(directoryEntry))
return getLongName(directoryEntry);
else
return getShortName(directoryEntry);
} //end getName(byte[] directoryEntry)
/**
* Returns the short name of the directory entry without the numeric tail.
* @param directoryEntry the given directory entry.
* @return the short name without the numeric tail.
*/
public static String getShortName(byte[] directoryEntry)
{
if (isDirectory(directoryEntry) || isRootDirectory(directoryEntry))
{
String str = ByteArrayConversionsLittleEndian.byteToString(
directoryEntry,
directoryEntry.length - 32 + 0,
directoryEntry.length - 32 + 11
);
int length = str.length();
//clear all last blanks
for (int i=0; i < length; i++)
if (str.endsWith(" "))
str = str.substring(0, str.length() - 1);
else
break;
return str;
}
else
return recreateFileName(
directoryEntry,
directoryEntry.length - 32 + 0,
directoryEntry.length - 32 + 11
);
} //getShortName(byte[] directoryEntry)
/**
* Returns the name of the file of a set of long name directory entries.
* the longEntry must be the whole set of long name directory entries
* (inclusive the short directory entry).
* @param longEntry a set of directory entries that together are a long
* name entry.
* @return the name of the long directory entry. In case the long name
* is not valid the null string is returned.
*/
public static String getLongName(byte[] longEntry)
{
String result = "";
//calculate the checksum of the basis-name. If there is one member of the
// the set of long directory entries that has not this checksum set. The
// long name is no longer valid.
byte[] basisName = new byte[11];
System.arraycopy(longEntry, longEntry.length-32, basisName, 0, 11);
byte checksum = (byte)checksum(basisName);
//extract the name
for (int i=longEntry.length-64; i >= 0; i-=32)
{
if (checksum != longEntry[i+13])
{
//for debug purposes
throw new RuntimeException ("Checksum doesn't match: "+Arrays.printHexArrayString(longEntry));
}
//get the first part
for (int j=i+1; j < i+11; j += 2)
if (longEntry[j] == 0 && longEntry[j+1] == 0)
return result + ByteArrayConversionsLittleEndian.unicodeByteToString(longEntry, i+1, j);
result += ByteArrayConversionsLittleEndian.unicodeByteToString(longEntry, i+1, i+11);
//get the second part
for (int j=i+14; j < i+26; j += 2)
if (longEntry[j] == 0 && longEntry[j+1] == 0)
return result + ByteArrayConversionsLittleEndian.unicodeByteToString(longEntry, i+14, j);
result += ByteArrayConversionsLittleEndian.unicodeByteToString(longEntry, i+14, i+26);
//get the third part
for (int j=i+28; j < i+32; j += 2)
if (longEntry[j] == 0 && longEntry[j+1] == 0)
return result + ByteArrayConversionsLittleEndian.unicodeByteToString(longEntry, i+28, j);
result += ByteArrayConversionsLittleEndian.unicodeByteToString(longEntry, i+28, i+32);
}
return result;
} //end getLongName((byte[] longEntry)
/**
* Return the attribute byte of the given directory entry.
* @param directoryEntry the given directory entry.
* @return the attribute.
*/
public static byte getAttribute(byte[] directoryEntry)
{
//index 11
return directoryEntry[directoryEntry.length - 32 + 11];
} //end getAttribute(byte[] directoryEntry)
/**
* Return the length of the file stored in the given directory
* entry.
* @param directoryEntry the given directory entry.
* @return the length of the file stored in the given directory
* entry.
*/
public static long getFileLength(byte[] directoryEntry)
{
//index 28 - 31
return ByteArrayConversionsLittleEndian.convLong(
directoryEntry[directoryEntry.length - 32 + 28],
directoryEntry[directoryEntry.length - 32 + 29],
directoryEntry[directoryEntry.length - 32 + 30],
directoryEntry[directoryEntry.length - 32 + 31]
);
} //end getSize(byte[] directoryEntry)
/**
* Set the length of the given directory entry to the given length.
* The method will only update the directory entry, but will not
* save the information in the directory structure.
* @param directoryEntry the given directory entry.
* @param fileLength the new length.
*/
public static void setFileLength(byte[] directoryEntry, long fileLength)
{
//index 28 - 31
directoryEntry[directoryEntry.length - 32 + 31] = (byte)((fileLength >> 32) & 0x00000000000000FF);
directoryEntry[directoryEntry.length - 32 + 30] = (byte)((fileLength >> 16) & 0x00000000000000FF);
directoryEntry[directoryEntry.length - 32 + 29] = (byte)((fileLength >> 8) & 0x00000000000000FF);
directoryEntry[directoryEntry.length - 32 + 28] = (byte)(fileLength & 0x00000000000000FF);
} //end setFileLength(byte[] directoryEntry, long fileLength)
/**
* Mark the given directory entry as free.
* @param directoryEntry the given directory entry.
*/
public static void setFree(byte[] directoryEntry)
{
for (int i=0; i < directoryEntry.length; i+= 32)
directoryEntry[i] = FREE_ENTRY;
} //end setFree(byte[] directoryEntry)
/**
* Return an iterator which lists all entries of the directory structure
* depending on the given list entry attribute. The iterator will return
* DirectoryEntryInformation objects, which contains all important information.
* @param listEntryAttribute specifies the kind of entries that should
* be returned within the iterator.
* @return Iterator which returns 32 byte arrays.
* @see xxl.core.io.fat.DirectoryEntryInformation
*/
public EntriesFilterIterator getEntries(int listEntryAttribute)
{
return new EntriesFilterIterator(
new ListEntriesIterator(
device,
bpb.getFirstRootDirSecNum(),
true
),
listEntryAttribute
);
} //end getAllEntries(int listEntryAttribute)
///////////////////////////////////
// DEBUG_METHODS //
///////////////////////////////////
/**
* Return the root directory as a byte array.
* @return the root directory as a byte array.
*/
public byte[] getRootDir()
{
if (bpb.getFatType() != FAT.FAT32)
{
byte[] result = new byte[bpb.getRootDirSectors()*bpb.BytsPerSec*bpb.SecPerClus];
Iterator clusters = new ClusterChainIterator(device, bpb.getFirstRootDirSecNum(), true);
int index = 0;
while (clusters.hasNext())
{
byte[] cluster = (byte[])clusters.next();
System.arraycopy(cluster, 0, result, index, cluster.length);
index += bpb.BytsPerSec*bpb.SecPerClus;
}
return result;
}
else
{
//first determines the number of bytes used for the root directory entry.
int clusterLength = 0;
Iterator clusters = new ClusterChainIterator(device, bpb.getFirstRootDirSecNum(), true);
while (clusters.hasNext())
clusterLength += ((byte[])clusters.next()).length;
//now copy each cluster of the root directory in one byte array
byte[] result = new byte[clusterLength];
int index = 0;
clusters = new ClusterChainIterator(device, bpb.getFirstRootDirSecNum(), true);
while (clusters.hasNext())
{
byte[] cluster = (byte[])clusters.next();
System.arraycopy(cluster, 0, result, index, cluster.length);
index += cluster.length;
}
return result;
}
} //end getRootDir()
} //end class DIR