/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.fs.fat;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Vector;
import org.jnode.fs.FSDirectory;
import org.jnode.fs.FSDirectoryId;
import org.jnode.fs.FSEntry;
import org.jnode.fs.ReadOnlyFileSystemException;
/**
* @author epr
*/
public abstract class AbstractDirectory extends FatObject implements FSDirectory, FSDirectoryId {
protected Vector<FatBasicDirEntry> entries = new Vector<FatBasicDirEntry>();
private boolean _dirty;
protected FatFile file;
// for root
protected AbstractDirectory(FatFileSystem fs, int nrEntries) {
super(fs);
entries.setSize(nrEntries);
_dirty = false;
}
protected AbstractDirectory(FatFileSystem fs, int nrEntries, FatFile file) {
this(fs, nrEntries);
this.file = file;
}
protected AbstractDirectory(FatFileSystem fs, FatFile myFile) {
this(fs, (int) myFile.getLength() / FatConstants.DIR_ENTRY_SIZE, myFile);
}
@Override
public String getDirectoryId() {
return Long.toString(file.getStartCluster());
}
/**
* Gets an iterator to iterate over all entries. The iterated objects are
* all instance DirEntry.
*
* @return Iterator
*/
public Iterator<FSEntry> iterator() {
return new DirIterator();
}
/**
* Add a directory entry.
*
* @param nameExt
* @throws IOException
*/
protected synchronized FatDirEntry addFatFile(String nameExt) throws IOException {
if (getFileSystem().isReadOnly()) {
throw new ReadOnlyFileSystemException("addFile in readonly filesystem");
}
if (getFatEntry(nameExt) != null) {
throw new IOException("File already exists" + nameExt);
}
final FatDirEntry newEntry = new FatDirEntry(this, splitName(nameExt), splitExt(nameExt));
int size = entries.size();
for (int i = 0; i < size; i++) {
FatBasicDirEntry e = entries.get(i);
if (e == null) {
entries.set(i, newEntry);
setDirty();
flush();
return newEntry;
}
}
int newSize = size + 512 / FatConstants.DIR_ENTRY_SIZE;
if (canChangeSize(newSize)) {
entries.ensureCapacity(newSize);
setDirty();
flush();
return newEntry;
}
throw new IOException("Directory is full");
}
/**
* Add a new file with a given name to this directory.
*
* @param name
* @throws IOException
*/
public FSEntry addFile(String name) throws IOException {
return addFatFile(name);
}
/**
* Add a directory entry of the type directory.
*
* @param nameExt
* @param parentCluster
* @throws IOException
*/
protected synchronized FatDirEntry addFatDirectory(String nameExt, long parentCluster) throws IOException {
final FatDirEntry entry = addFatFile(nameExt);
final int clusterSize = getFatFileSystem().getClusterSize();
entry.setFlags(FatConstants.F_DIRECTORY);
final FatFile file = entry.getFatFile();
file.setLength(clusterSize);
//TODO optimize it also to use ByteBuffer at lower level
//final byte[] buf = new byte[clusterSize];
final ByteBuffer buf = ByteBuffer.allocate(clusterSize);
// Clean the contents of this cluster to avoid reading strange data
// in the directory.
//file.write(0, buf, 0, buf.length);
file.write(0, buf);
file.getDirectory().initialize(file.getStartCluster(), parentCluster);
flush();
return entry;
}
/**
* Add a new (sub-)directory with a given name to this directory.
*
* @param name
* @throws IOException
*/
public FSEntry addDirectory(String name) throws IOException {
if (getFileSystem().isReadOnly()) {
throw new ReadOnlyFileSystemException("addDirectory in readonly filesystem");
}
final long parentCluster;
if (file == null) {
parentCluster = 0;
} else {
parentCluster = file.getStartCluster();
}
return addFatDirectory(name, parentCluster);
}
/**
* Gets the number of directory entries in this directory
*
* @return int
*/
public int getSize() {
return entries.size();
}
/**
* Search for an entry with a given name.ext
*
* @param nameExt
* @return FatDirEntry null == not found
*/
protected FatDirEntry getFatEntry(String nameExt) {
final String name = splitName(nameExt);
final String ext = splitExt(nameExt);
int size = entries.size();
for (int i = 0; i < size; i++) {
final FatBasicDirEntry entry = entries.get(i);
if (entry != null && entry instanceof FatDirEntry) {
FatDirEntry fde = (FatDirEntry) entry;
if (name.equalsIgnoreCase(fde.getNameOnly()) && ext.equalsIgnoreCase(fde.getExt())) {
return fde;
}
}
}
return null;
}
/**
* Gets the entry with the given name.
*
* @param name
* @throws IOException
*/
public FSEntry getEntry(String name) throws IOException {
final FatDirEntry entry = getFatEntry(name);
if (entry == null) {
throw new FileNotFoundException(name);
} else {
return entry;
}
}
/**
* Remove a file or directory with a given name
*
* @param nameExt
*/
public synchronized void remove(String nameExt) throws IOException {
FatDirEntry entry = getFatEntry(nameExt);
if (entry == null) {
throw new FileNotFoundException(nameExt);
}
for (int i = 0; i < entries.size(); i++) {
if (entries.get(i) == entry) {
entries.set(i, null);
setDirty();
flush();
return;
}
}
}
/**
* Print the contents of this directory to the given writer. Used for
* debugging purposes.
*
* @param out
*/
public void printTo(PrintWriter out) {
int freeCount = 0;
int size = entries.size();
for (int i = 0; i < size; i++) {
FatBasicDirEntry entry = entries.get(i);
if (entry != null) {
out.println("0x" + Integer.toHexString(i) + " " + entries.get(i));
} else {
freeCount++;
}
}
out.println("Unused entries " + freeCount);
}
class DirIterator implements Iterator<FSEntry> {
private int offset = 0;
/**
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
int size = entries.size();
while (offset < size) {
FatBasicDirEntry e = entries.get(offset);
if ((e != null) && e instanceof FatDirEntry && !((FatDirEntry) e).isDeleted()) {
return true;
} else {
offset++;
}
}
return false;
}
/**
* @see java.util.Iterator#next()
*/
public FSEntry next() {
int size = entries.size();
while (offset < size) {
FatBasicDirEntry e = entries.get(offset);
if ((e != null) && (e instanceof FatDirEntry) && !((FatDirEntry) e).isDeleted()) {
offset++;
return (FSEntry) e;
} else {
offset++;
}
}
throw new NoSuchElementException();
}
/**
* @see java.util.Iterator#remove()
*/
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Returns the dirty.
*
* @return boolean
*/
public boolean isDirty() {
if (_dirty) {
return true;
}
int size = entries.size();
for (int i = 0; i < size; i++) {
FatBasicDirEntry entry = entries.get(i);
if ((entry != null) && (entry instanceof FatDirEntry)) {
if (((FatDirEntry) entry).isDirty()) {
return true;
}
}
}
return false;
}
/**
* Mark this directory as dirty.
*/
protected final void setDirty() {
this._dirty = true;
}
/**
* Mark this directory as not dirty.
*/
protected final void resetDirty() {
this._dirty = false;
}
/**
* Can this directory change size of <code>newSize</code> directory
* entries?
*
* @param newSize
* @return boolean
*/
protected abstract boolean canChangeSize(int newSize);
protected String splitName(String nameExt) {
int i = nameExt.indexOf('.');
if (i < 0) {
return nameExt;
} else {
return nameExt.substring(0, i);
}
}
protected String splitExt(String nameExt) {
int i = nameExt.indexOf('.');
if (i < 0) {
return "";
} else {
return nameExt.substring(i + 1);
}
}
/**
* Sets the first two entries '.' and '..' in the directory
*
* @param parentCluster
*/
protected void initialize(long myCluster, long parentCluster) {
FatDirEntry e = new FatDirEntry(this, ".", "");
entries.set(0, e);
e.setFlags(FatConstants.F_DIRECTORY);
e.setStartCluster((int) myCluster);
e = new FatDirEntry(this, "..", "");
entries.set(1, e);
e.setFlags(FatConstants.F_DIRECTORY);
e.setStartCluster((int) parentCluster);
}
/**
* Flush the contents of this directory to the persistent storage
*/
public abstract void flush() throws IOException;
/**
* Read the contents of this directory from the given byte array
*
* @param src
*/
protected synchronized void read(byte[] src) {
int size = entries.size();
for (int i = 0; i < size; i++) {
int index = i * FatConstants.DIR_ENTRY_SIZE;
if (src[index] == 0) {
entries.set(i, null);
} else {
FatBasicDirEntry entry = FatDirEntry.fatDirEntryFactory(this, src, index);
entries.set(i, entry);
}
}
}
/**
* Write the contents of this directory to the given device at the given
* offset.
*
* @param dest
*/
protected synchronized void write(byte[] dest) {
int size = entries.size();
byte[] empty = new byte[FatConstants.DIR_ENTRY_SIZE];
for (int i = 0; i < size; i++) {
FatBasicDirEntry entry = entries.get(i);
if (entry != null) {
entry.write(dest, i * FatConstants.DIR_ENTRY_SIZE);
} else {
System.arraycopy(empty, 0, dest, i * FatConstants.DIR_ENTRY_SIZE, empty.length);
}
}
}
}