diff servlet/src/digilib/io/FileOps.java @ 339:6d2032b6121d gen2_1

new directory and cache work
author robcast
date Wed, 17 Nov 2004 18:17:34 +0100
parents 0ff3ede32060
children
line wrap: on
line diff
--- a/servlet/src/digilib/io/FileOps.java	Thu Jan 17 15:25:46 2002 +0100
+++ b/servlet/src/digilib/io/FileOps.java	Wed Nov 17 18:17:34 2004 +0100
@@ -1,188 +1,675 @@
-/*  FileOps -- Utility class for file operations
-
-  Digital Image Library servlet components
-
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
-
-  This program is free software; you can redistribute  it and/or modify it
-  under  the terms of  the GNU General  Public License as published by the
-  Free Software Foundation;  either version 2 of the  License, or (at your
-  option) any later version.
-   
-  Please read license.txt for the full details. A copy of the GPL
-  may be found at http://www.gnu.org/copyleft/lgpl.html
-
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-*/
+/*
+ * FileOps -- Utility class for file operations
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ */
 
 package digilib.io;
 
-import java.io.*;
-import java.util.*;
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
 
-import digilib.*;
-
+import org.apache.log4j.Logger;
 
 public class FileOps {
 
-  private Utils util = null;
-  public static String[] fileTypes = {
-            "jpg", "image/jpeg",
-            "jpeg", "image/jpeg",
-            "png", "image/png",
-            "gif", "image/gif",
-            "tif", "image/tiff",
-            "tiff", "image/tiff"};
+	private static Logger logger = Logger.getLogger(FileOps.class);
+	
+	/**
+	 * Array of file extensions and corresponding mime-types.
+	 */
+	private static String[][] ft = { { "jpg", "image/jpeg" },
+			{ "jpeg", "image/jpeg" }, { "jp2", "image/jp2" },
+			{ "png", "image/png" }, { "gif", "image/gif" },
+			{ "tif", "image/tiff" }, { "tiff", "image/tiff" },
+			{ "txt", "text/plain" }, { "html", "text/html" },
+			{ "htm", "text/html" }, { "xml", "text/xml" },
+			{ "svg", "image/svg+xml" } };
+
+	public static Map fileTypes;
+
+	public static List imageExtensions;
+
+	public static List textExtensions;
+
+	public static List svgExtensions;
+
+	public static final int CLASS_NONE = -1;
+
+	public static final int CLASS_IMAGE = 0;
+
+	public static final int CLASS_TEXT = 1;
+
+	public static final int CLASS_SVG = 2;
+
+	public static final int NUM_CLASSES = 3;
 
-  public FileOps() {
-    util = new Utils();
-  }
+	public static int[] fileClasses = {};
+	
+	public static int[] fcIndexes = {};
+
+	public static final Integer HINT_BASEDIRS = new Integer(1);
+
+	public static final Integer HINT_FILEEXT = new Integer(2);
+
+	public static final Integer HINT_DIRS = new Integer(3);
+
+	public static File[] baseDirs = {};
+	
+	public static DocuDirCache cache;
 
-  public FileOps(Utils u) {
-    util = u;
-  }
+	/**
+	 * static initializer for FileOps
+	 */
+	static {
+		fileTypes = new HashMap();
+		imageExtensions = new ArrayList();
+		textExtensions = new ArrayList();
+		svgExtensions = new ArrayList();
+		// iterate through file types in ft and fill the Map and Lists
+		for (int i = 0; i < ft.length; i++) {
+			String ext = ft[i][0];
+			String mt = ft[i][1];
+			fileTypes.put(ext, mt);
+			if (classForMimetype(mt) == CLASS_IMAGE) {
+				imageExtensions.add(ext);
+			} else if (classForMimetype(mt) == CLASS_TEXT) {
+				textExtensions.add(ext);
+			} else if (classForMimetype(mt) == CLASS_SVG) {
+				svgExtensions.add(ext);
+			}
+		}
+	}
 
-  public void setUtils(Utils u) {
-    util = u;
-  }
+	/** sets the array of actually used file classes */
+	public static void setFileClasses(int[] fc) {
+		fileClasses = fc;
+		fcIndexes = new int[NUM_CLASSES];
+		for (int i = 0; i < fc.length; i++) {
+			fcIndexes[fc[i]] = i;
+		}
+	}
 
+	/** returns the array of actually used file classes */
+	public static int[] getFileClasses() {
+		return fileClasses;
+	}
+
+	/** returns an element from the array of actally used file classes */
+	public static int getFileClass(int idx) {
+		try {
+			return fileClasses[idx];
+		} catch (Exception e) {
+		}
+		return CLASS_NONE;
+	}
 
-  /**
-   *  get the mime type for a file format (by extension)
-   */
-  public static String mimeForFile(File f) {
-    String fn = f.getName();
-    for (int i = 0; i < fileTypes.length; i += 2) {
-      if (fn.toLowerCase().endsWith(fileTypes[i])) {
-        return fileTypes[i+1];
-      }
-    }
-    return null;
-  }
+	/** Returns the index number for the given file class.
+	 * @param fc
+	 * @return
+	 */
+	public static int getFCindex(int fc) {
+		try {
+			return fcIndexes[fc];
+		} catch (Exception e) {
+		}
+		return -1;
+	}
+	
+	/**
+	 * returns the file class for a mime-type
+	 * 
+	 * @param mt
+	 * @return
+	 */
+	public static int classForMimetype(String mt) {
+		if (mt == null) {
+			return CLASS_NONE;
+		}
+		if (mt.startsWith("image/svg")) {
+			return CLASS_SVG;
+		} else if (mt.startsWith("image")) {
+			return CLASS_IMAGE;
+		} else if (mt.startsWith("text")) {
+			return CLASS_TEXT;
+		}
+		return CLASS_NONE;
+	}
+
+	/**
+	 * get the mime type for a file format (by extension)
+	 */
+	public static String mimeForFile(File f) {
+		return (String) fileTypes.get(extname(f.getName().toLowerCase()));
+	}
+
+	/**
+	 * get the file class for the filename (by extension)
+	 * 
+	 * @param fn
+	 * @return
+	 */
+	public static int classForFilename(String fn) {
+		String mt = (String) fileTypes.get(extname(fn).toLowerCase());
+		return classForMimetype(mt);
+	}
 
-  /**
-   *  get a filehandle for a file or directory name
-   *    returns File number n if fn is directory (starts with 1)
-   */
-  public File getFile(String fn, int n) throws FileOpException {
-    util.dprintln(4, "getFile ("+fn+", "+n+")");
+	/**
+	 * get the file class for the file (by extension)
+	 * 
+	 * @param fn
+	 * @return
+	 */
+	public static int classForFile(File f) {
+		return classForFilename(f.getName());
+	}
+
+	public static Iterator getImageExtensionIterator() {
+		return imageExtensions.iterator();
+	}
+
+	public static Iterator getTextExtensionIterator() {
+		return textExtensions.iterator();
+	}
+
+	public static Iterator getSVGExtensionIterator() {
+		return svgExtensions.iterator();
+	}
+
+	/**
+	 * convert a string with a list of pathnames into an array of strings using
+	 * the system's path separator string
+	 */
+	public static String[] pathToArray(String paths) {
+		// split list into directories
+		StringTokenizer dirs = new StringTokenizer(paths, File.pathSeparator);
+		int n = dirs.countTokens();
+		if (n < 1) {
+			return null;
+		}
+		// add directories into array
+		String[] pathArray = new String[n];
+		for (int i = 0; i < n; i++) {
+			String s = dirs.nextToken();
+			// make shure the dir name ends with a directory separator
+			if (s.endsWith(File.separator)) {
+				pathArray[i] = s;
+			} else {
+				pathArray[i] = s + File.separator;
+			}
+		}
+		return pathArray;
+	}
+
+	/**
+	 * Extract the base of a file name (sans extension).
+	 * 
+	 * Returns the filename without the extension. The extension is the part
+	 * behind the last dot in the filename. If the filename has no dot the full
+	 * file name is returned.
+	 * 
+	 * @param fn
+	 * @return
+	 */
+	public static String basename(String fn) {
+		if (fn == null) {
+			return null;
+		}
+		int i = fn.lastIndexOf('.');
+		if (i > 0) {
+			return fn.substring(0, i);
+		}
+		return fn;
+	}
 
-    File f = new File(fn);
-    // if fn is a file name then return file
-    if (f.isFile()) {
-      return f;
-    }
-    // if fn is a directory name then open directory
-    if (f.isDirectory()) {
-      File[] fl = f.listFiles(new ImgFileFilter());
-      Arrays.sort(fl);
-      if ((n > 0) && (n <= fl.length)) {
-         return fl[n - 1];
-      }
-    }
-    throw new FileOpException("Unable to find file: "+fn);
-  }
+	/**
+	 * Extract the base of a file name (sans extension).
+	 * 
+	 * Returns the filename without the extension. The extension is the part
+	 * behind the last dot in the filename. If the filename has no dot the full
+	 * file name is returned.
+	 * 
+	 * @param f
+	 * @return
+	 */
+	public static String basename(File f) {
+		if (f == null) {
+			return null;
+		}
+		return basename(f.getName());
+	}
+
+	/**
+	 * Extract the extension of a file name.
+	 * 
+	 * Returns the extension of a file name. The extension is the part behind
+	 * the last dot in the filename. If the filename has no dot the empty string
+	 * is returned.
+	 * 
+	 * @param fn
+	 * @return
+	 */
+	public static String extname(String fn) {
+		if (fn == null) {
+			return null;
+		}
+		int i = fn.lastIndexOf('.');
+		if (i > 0) {
+			return fn.substring(i + 1);
+		}
+		return "";
+	}
 
-  /**
-   *  get the number of files in a directory
-   *    (almost the same as getFile)
-   *  returns 0 in case of problems
-   */
-  public int getNumFiles(String fn) throws FileOpException {
-    util.dprintln(4, "getNumFiles ("+fn+")");
+	/**
+	 * Extract the parent directory of a (digilib) path name.
+	 * 
+	 * Returns the parent directory of a path name. The parent is the part
+	 * before the last slash in the path name. If the path name has no slash the
+	 * empty string is returned.
+	 * 
+	 * @param fn
+	 * @return
+	 */
+	public static String dlParent(String fn) {
+		if (fn == null) {
+			return null;
+		}
+		int i = fn.lastIndexOf('/');
+		if (i > 0) {
+			return fn.substring(0, i);
+		}
+		return "";
+	}
 
-    File f = new File(fn);
-    // if fn is a file name then return 1
-    if (f.isFile()) {
-      return 1;
-    }
-    // if fn is a directory name then return the number of files
-    if (f.isDirectory()) {
-      return f.listFiles(new ImgFileFilter()).length;
-    }
-    // then fn must be something strange...
-    return 0;
-  }
+	/**
+	 * Extract the dir/file name of a (digilib) path name.
+	 * 
+	 * The file/dir name is the part after the last slash in the path name. If
+	 * the path name has no slash the same string is returned.
+	 * 
+	 * @param path
+	 * @return
+	 */
+	public static String dlName(String path) {
+		if (path == null) {
+			return null;
+		}
+		int i = path.lastIndexOf('/');
+		if (i > 0) {
+			return path.substring(i+1);
+		}
+		return path;
+	}
 
+	/**
+	 * Normalize a path name.
+	 * 
+	 * Removes leading and trailing slashes. Returns null if there is other
+	 * unwanted stuff in the path name.
+	 * 
+	 * @param pathname
+	 * @return
+	 */
+	public static String normalName(String pathname) {
+		if (pathname == null) {
+			return null;
+		}
+		// upper-dir references are unwanted
+		if (pathname.indexOf("../") >= 0) {
+			return null;
+		}
+		int a = 0;
+		int e = pathname.length() - 1;
+		if (e < 0) {
+			return pathname;
+		}
+		// leading and trailing "/" are removed
+		while ((a <= e) && (pathname.charAt(a) == '/')) {
+			a++;
+		}
+		while ((a < e) && (pathname.charAt(e) == '/')) {
+			e--;
+		}
+		return pathname.substring(a, e + 1);
+	}
 
-  /**
-   *  get a filehandle for a file or directory name out of a list
-   *    dirs is a list of base directories, fn is the appended file/dirname
-   *    searches dirs until fn exists (backwards if fwd is false)
-   *    returns File number n if fn is directory (starts with 1)
-   */
-  public File getFileVariant(String[] dirs, String fn, int n, boolean fwd) throws FileOpException {
-    util.dprintln(4, "getVariantFile ("+dirs+", "+fn+", "+n+")");
+	public static StringTokenizer dlPathIterator(String path) {
+		return new StringTokenizer(path, "/");
+	}
+	
+	
+	/**
+	 * FileFilter for general files
+	 */
+	static class ReadableFileFilter implements FileFilter {
+
+		public boolean accept(File f) {
+			return f.canRead();
+		}
+	}
+
+	/**
+	 * FileFilter for image types (helper class for getFile)
+	 */
+	static class ImageFileFilter implements FileFilter {
+
+		public boolean accept(File f) {
+			return (classForFilename(f.getName()) == CLASS_IMAGE);
+		}
+	}
+
+	/**
+	 * FileFilter for text types (helper class for getFile)
+	 */
+	static class TextFileFilter implements FileFilter {
+
+		public boolean accept(File f) {
+			return (classForFilename(f.getName()) == CLASS_TEXT);
+		}
+	}
+
+	/**
+	 * FileFilter for svg types (helper class for getFile).
+	 *  
+	 */
+	static class SVGFileFilter implements FileFilter {
 
-    File f = null;
-    int start = 0;
-    int inc = 1;
-    int end = dirs.length;
-    if (fwd == false) {
-      start = dirs.length - 1;
-      inc = -1;
-      end = 0;
-    }
+		public boolean accept(File f) {
+			return (classForFilename(f.getName()) == CLASS_SVG);
+		}
+	}
+
+	/**
+	 * Factory for FileFilters (image or text).
+	 * 
+	 * @param fileClass
+	 * @return
+	 */
+	public static FileFilter filterForClass(int fileClass) {
+		if (fileClass == CLASS_IMAGE) {
+			return new ImageFileFilter();
+		}
+		if (fileClass == CLASS_TEXT) {
+			return new TextFileFilter();
+		}
+		if (fileClass == CLASS_SVG) {
+			return new SVGFileFilter();
+		}
+		return null;
+	}
 
-    for (int i = start; i != end; i += inc) {
-      try {
-        f = getFile(dirs[i]+fn, n);
-      } catch (FileOpException e) {
-        f = null;
-      }
-      if (f != null) {
-        return f;
-      }
-    }
-    throw new FileOpException("Unable to find file: "+fn);
-  }
+	/**
+	 * Factory for DocuDirents based on file class.
+	 * 
+	 * Returns an ImageFileset, TextFile or SVGFile.
+	 * 
+	 * @param fileClass
+	 * @param file
+	 * @param parent
+	 * @param hints
+	 *            optional additional parameters
+	 * @return
+	 */
+	public static DigiDirent fileForClass(int fileClass, File file,
+			DigiDirectory parent, Map hints) {
+		// what class of file do we have?
+		if (fileClass == CLASS_IMAGE) {
+			// image file
+			return new ImageFileset(file, parent, hints);
+		} else if (fileClass == CLASS_TEXT) {
+			// text file
+			return new TextFile(file, parent);
+		} else if (fileClass == CLASS_SVG) {
+			// text file
+			return new SVGFile(file, parent);
+		}
+		// anything else is a generic dir or file
+		if (file.isDirectory()) {
+			return getCachedDirectory(file, null, parent);
+		}
+		return new DigiDirent(file.getName(), parent);
+	}
+
+	/**
+	 * Filters a list of Files through a FileFilter.
+	 * 
+	 * @param files
+	 * @param filter
+	 * @return
+	 */
+	public static File[] listFiles(File[] files, FileFilter filter) {
+		if (files == null) {
+			return null;
+		}
+		File[] ff = new File[files.length];
+		int ffi = 0;
+		for (int i = 0; i < files.length; i++) {
+			if (filter.accept(files[i])) {
+				ff[ffi] = files[i];
+				ffi++;
+			}
+		}
+		File[] fff = new File[ffi];
+		System.arraycopy(ff, 0, fff, 0, ffi);
+		return fff;
+	}
 
-  /**
-   *  get the number of files in a directory
-   *    (almost the same as getFileVariant)
-   *  returns 0 in case of problems
-   */
-  public int getNumFilesVariant(String[] dirs, String fn, boolean fwd) throws FileOpException {
-    util.dprintln(4, "getNumFilesVariant ("+dirs+", "+fn+")");
+	/**
+	 * Returns the closest matching file out of an array.
+	 * 
+	 * Compares the files sans extensions if no direct match is found. Returns
+	 * null if no match is found.
+	 * 
+	 * @param fn
+	 * @param files
+	 * @return
+	 */
+	public static File findFile(File fn, File[] files) {
+		// try the same filename as the original
+		int fileIdx = Arrays.binarySearch(files, fn);
+		if (fileIdx >= 0) {
+			return files[fileIdx];
+		} else {
+			// try closest matches without extension
+			String fb = FileOps.basename(fn);
+			fileIdx = -fileIdx - 1;
+			if ((fileIdx < files.length)
+					&& (FileOps.basename(files[fileIdx]).equals(fb))) {
+				// idx ok
+				return files[fileIdx];
+			} else if ((fileIdx > 0)
+					&& (FileOps.basename(files[fileIdx - 1]).equals(fb))) {
+				// idx-1 ok
+				return files[fileIdx - 1];
+			} else if ((fileIdx + 1 < files.length)
+					&& (FileOps.basename(files[fileIdx + 1]).equals(fb))) {
+				// idx+1 ok
+				return files[fileIdx + 1];
+			}
+		}
+		// unknown
+		return null;
+	}
 
-    int nf = 0;
-    int start = 0;
-    int inc = 1;
-    int end = dirs.length;
-    if (fwd == false) {
-      start = dirs.length - 1;
-      inc = -1;
-      end = 0;
-    }
+	/**
+	 * Returns the closest matching file out of an array.
+	 * 
+	 * Compares the files sans extensions if no direct match is found. Returns
+	 * null if no match is found.
+	 * 
+	 * @param fn
+	 * @param files
+	 * @return
+	 */
+	public static String findFilename(String fn, String[] files) {
+		// try the same filename as the original
+		int fileIdx = Arrays.binarySearch(files, fn);
+		if (fileIdx >= 0) {
+			return files[fileIdx];
+		} else {
+			// try closest matches without extension
+			String fb = FileOps.basename(fn);
+			fileIdx = -fileIdx - 1;
+			if ((fileIdx < files.length)
+					&& (FileOps.basename(files[fileIdx]).equals(fb))) {
+				// idx ok
+				return files[fileIdx];
+			} else if ((fileIdx > 0)
+					&& (FileOps.basename(files[fileIdx - 1]).equals(fb))) {
+				// idx-1 ok
+				return files[fileIdx - 1];
+			} else if ((fileIdx + 1 < files.length)
+					&& (FileOps.basename(files[fileIdx + 1]).equals(fb))) {
+				// idx+1 ok
+				return files[fileIdx + 1];
+			}
+		}
+		// unknown
+		return null;
+	}
+	/**
+	 * Returns a File for a base directory and a digilib-path.
+	 * 
+	 * @param basedir
+	 * @param dlpath
+	 * @return
+	 */
+	public static File getRealFile(File basedir, String dlpath) {
+		// does this work on all platforms??
+		return new File(basedir, dlpath);
+	}
+
+	/** Returns a File for a digilib-path.
+	 * 
+	 * The file is assumed to be in the first base directory.
+	 * 
+	 * @param dlpath
+	 * @return
+	 */
+	public static File getRealFile(String dlpath) {
+		// does this work on all platforms??
+		return new File(baseDirs[0], dlpath);
+	}
 
-    for (int i = start; i != end; i += inc) {
-      try {
-        nf = getNumFiles(dirs[i]+fn);
-      } catch (FileOpException e) {
-        nf = 0;
-      }
-      if (nf > 0) {
-        return nf;
-      }
-    }
-    return 0;
-  }
+	/**
+	 * Creates a new empty hints Map.
+	 * 
+	 * @return
+	 */
+	public static Map newHints() {
+		Map m = new HashMap();
+		return m;
+	}
+
+	/**
+	 * Creates a new hints Map with the given first element.
+	 * 
+	 * @param type
+	 * @param value
+	 * @return
+	 */
+	public static Map newHints(Integer type, Object value) {
+		Map m = new HashMap();
+		if (type != null) {
+			m.put(type, value);
+		}
+		return m;
+	}
+
+	/**
+	 * @return Returns the baseDirs.
+	 */
+	public static File[] getBaseDirs() {
+		return baseDirs;
+	}
+
+	/**
+	 * @param baseDirs
+	 *            The baseDirs to set.
+	 */
+	public static void setBaseDirs(File[] baseDirs) {
+		FileOps.baseDirs = baseDirs;
+	}
 
-  /**
-   *  FileFilter for image types (helper class for getFile)
-   */
-  private class ImgFileFilter implements FileFilter {
-
-    public boolean accept(File f) {
-      if (f.isFile()) {
-        return (mimeForFile(f) != null);
-      } else {
-        return false;
-      }
-    }
-  }
-
+	
+	/**
+	 * Returns a DigiDirectory instance that is guaranteed to be unique in the
+	 * cache.
+	 * 
+	 * @param dir
+	 * @param parent
+	 * @return
+	 */
+	public static DigiDirectory getCachedDirectory(File dir, String dlpath, DigiDirectory parent) {
+		if (dir == null) {
+			dir = FileOps.getRealFile(dlpath);
+		}
+		DigiDirectory dd = null;
+		if (parent == null) {
+			// create a new parent by starting at the root
+			StringBuffer ps = new StringBuffer();
+			DigiDirectory p = cache.getRootDir();
+			// walk the path
+			for (StringTokenizer i = dlPathIterator(dlpath); i.hasMoreTokens();) {
+				p.check();
+				String dn = i.nextToken();
+				ps.append("/");
+				ps.append(dn);
+				DigiDirectory d = cache.get(dn);
+				if (d == null) {
+					dd = new DigiDirectory(FileOps.getRealFile(dn), ps.toString(), p);
+				}
+				if (d.getParent() != p) {
+					logger.warn("digidirectory "+d.getDLPath()+" has wrong parent: "+p.getDLPath());
+				}
+				p = d;
+			}
+		} else {
+			if (dlpath == null) {
+				dlpath = parent.getDLPath() + "/" + dir.getName();
+			}
+			dd = cache.get(dlpath);
+			if (dd == null) {
+				dd = new DigiDirectory(dir, dlpath, parent);
+			} else {
+				logger.debug("reusing directory:" + dlpath);
+			}
+		}
+		return dd;
+	}
+	
+	/**
+	 * @return Returns the cache.
+	 */
+	public static DocuDirCache getCache() {
+		return cache;
+	}
+	/**
+	 * @param cache The cache to set.
+	 */
+	public static void setCache(DocuDirCache cache) {
+		FileOps.cache = cache;
+	}
 }