/* * $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.spi; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.util.Iterator; import org.apache.log4j.Logger; import org.jnode.fs.FSDirectory; import org.jnode.fs.FSEntry; import org.jnode.fs.ReadOnlyFileSystemException; /** * An abstract implementation of FSDirectory that contains common things among * many FileSystems * * @author Fabien DUMINY */ public abstract class AbstractFSDirectory extends AbstractFSObject implements FSDirectory { private static final Logger log = Logger.getLogger(AbstractFSDirectory.class); /* Table of entries */ private FSEntryTable entries = FSEntryTable.EMPTY_TABLE; /* Is this directory a root-directory? */ private boolean isRoot; /** * Constructor for a new non-root directory * * @param fs */ public AbstractFSDirectory(AbstractFileSystem<?> fs) { this(fs, false); } /** * Constructor for a new directory (root or non-root) * * @param fs * @param root true if it's a root directory */ public AbstractFSDirectory(AbstractFileSystem<?> fs, boolean root) { super(fs); this.isRoot = root; } /** * Print the contents of this directory to the given writer. Used for * debugging purposes. * * @param out */ public final void printTo(PrintWriter out) { checkEntriesLoaded(); int freeCount = 0; int size = entries.size(); for (int i = 0; i < size; i++) { FSEntry entry = entries.get(i); if (entry != null) { out.println("0x" + Integer.toHexString(i) + " " + entry); } else { freeCount++; } } out.println("Unused entries " + freeCount); } /** * @see org.jnode.fs.FSDirectory#iterator() */ public final Iterator<FSEntry> iterator() throws IOException { checkEntriesLoaded(); return entries.iterator(); } /** * Is this directory a root ? * * @return if this directory is the root */ public final boolean isRoot() { return isRoot; } /** * Gets the entry with the given name. * * @see org.jnode.fs.FSDirectory#getEntry(java.lang.String) */ public final FSEntry getEntry(String name) throws IOException { // ensure entries are loaded from BlockDevice checkEntriesLoaded(); return entries.get(name); } /** * Add a new directory with a given name * * @param name * @return the new added directory * @throws IOException */ public final synchronized FSEntry addDirectory(String name) throws IOException { log.debug("<<< BEGIN addDirectory " + name + " >>>"); if (!canWrite()) throw new ReadOnlyFileSystemException("Filesystem or directory is mounted read-only!"); if (getEntry(name) != null) { throw new IOException("File or Directory already exists" + name); } FSEntry newEntry = createDirectoryEntry(name); setFreeEntry(newEntry); log.debug("<<< END addDirectory " + name + " >>>"); return newEntry; } /** * Remove a file or directory with a given name * * @param name * @throws IOException */ public synchronized void remove(String name) throws IOException { if (!canWrite()) throw new IOException("Filesystem or directory is mounted read-only!"); if (entries.remove(name) >= 0) { setDirty(); flush(); return; } else throw new FileNotFoundException(name); } /** * Flush the contents of this directory to the persistent storage * * @throws IOException */ public final void flush() throws IOException { if (canWrite()) { boolean flushEntries = isEntriesLoaded() && entries.isDirty(); if (isDirty() || flushEntries) { writeEntries(entries); entries.resetDirty(); resetDirty(); } } } /** * Read the entries of this directory from the persistent storage * * @return a list of entries for this directory * @throws IOException */ protected abstract FSEntryTable readEntries() throws IOException; /** * Write the entries of this directory to the persistent storage * * @param entries a list of entries for this directory * @throws IOException */ protected abstract void writeEntries(FSEntryTable entries) throws IOException; /** * Is this directory dirty (ie is there any data to save to device) ? * * @return if this directory is dirty * @throws IOException */ public final boolean isDirty() throws IOException { if (super.isDirty()) { return true; } // If entries are not loaded, they are clean (ie: in the storage) ! if (isEntriesLoaded() && entries.isDirty()) return true; return false; } /** * BE CAREFULL : don't call this method from the constructor of this class * because it call the method readEntries of the child classes that are not * yet initialized (constructed). */ protected final void checkEntriesLoaded() { log.debug("<<< BEGIN checkEntriesLoaded >>>"); if (!isEntriesLoaded()) { log.debug("checkEntriesLoaded : loading"); try { if (canRead()) { entries = readEntries(); } else { // the next time, we will call checkEntriesLoaded() // we will retry to load entries entries = FSEntryTable.EMPTY_TABLE; log.debug("checkEntriesLoaded : can't read, using EMPTY_TABLE"); } resetDirty(); } catch (IOException e) { log.fatal("unable to read directory entries", e); // the next time, we will call checkEntriesLoaded() // we will retry to load entries entries = FSEntryTable.EMPTY_TABLE; } } log.debug("<<< END checkEntriesLoaded >>>"); } /** * Have we already loaded our entries from device ? * * @return if the entries are allready loaded from the device */ private final boolean isEntriesLoaded() { return (entries != FSEntryTable.EMPTY_TABLE); } /** * Add a new file with a given name * * @param name * @throws IOException * @return the added file entry */ public final synchronized FSEntry addFile(String name) throws IOException { log.debug("<<< BEGIN addFile " + name + " >>>"); if (!canWrite()) throw new ReadOnlyFileSystemException("Filesystem or directory is mounted read-only!"); if (getEntry(name) != null) { throw new IOException("File or directory already exists: " + name); } FSEntry newEntry = createFileEntry(name); setFreeEntry(newEntry); log.debug("<<< END addFile " + name + " >>>"); return newEntry; } /** * Abstract method to create a new file entry from the given name * * @param name * @return the new created file * @throws IOException */ protected abstract FSEntry createFileEntry(String name) throws IOException; /** * Abstract method to create a new directory entry from the given name * @param name * @return the new created directory * @throws IOException */ protected abstract FSEntry createDirectoryEntry(String name) throws IOException; /** * Find a free entry and set it with the given entry * @param newEntry * @throws IOException */ private final void setFreeEntry(FSEntry newEntry) throws IOException { checkEntriesLoaded(); if (entries.setFreeEntry(newEntry) >= 0) { log.debug("setFreeEntry: free entry found !"); // a free entry has been found setDirty(); flush(); return; } } /** * Return our entry table * @return the entry table */ protected FSEntryTable getEntryTable() { return entries; } /** * @return a string representation of this instance. */ public String toString() { return entries.toString(); } }