Java tutorial
/* * Copyright (c) 2004-2010 The Regents of the University of California. * All rights reserved. * * '$Author: welker $' * '$Date: 2010-05-05 22:21:26 -0700 (Wed, 05 May 2010) $' * '$Revision: 24234 $' * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the above * copyright notice and the following two paragraphs appear in all copies * of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, * ENHANCEMENTS, OR MODIFICATIONS. * */ package org.kepler.ssh; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Local file delete according to a regular expression. This class can delete * files and directories recursively. E.g /home/dummy/dir?/sub*d??/f* Relative * pathes are handled as relative to the current dir. */ public class LocalDelete { public LocalDelete() { } public boolean deleteFiles(String mask, boolean recursive) throws ExecException { if (mask == null) return true; if (mask.trim() == "") return true; // pre-test to conform to 'rm -rf': if the mask ends for . or .. // it must throw an error String name = new File(mask).getName(); if (name.equals(".") || name.equals("..")) { throw new ExecException("Directories like . or .. are not allowed to be removed: " + mask); } // split the mask into a vector of single masks // e.g. a/b*d/c?? into (a, b*d, c??) Vector splittedMask = splitMask(mask); // sure it has at least one element: . String path = (String) splittedMask.firstElement(); splittedMask.remove(0); return delete(new File(path), splittedMask, recursive); } /********************/ /* Private methods */ /********************/ private static final Log log = LogFactory.getLog(LocalDelete.class.getName()); private static final boolean isDebugging = log.isDebugEnabled(); /** * Return the first position of * or ? in the string. returns -1 if none * found or the string is null. */ private static int wildcardPos(String mask) { if (mask == null) return -1; int firstStarIdx = mask.indexOf("*"); int firstQmIdx = mask.indexOf("?"); if (firstStarIdx == -1) return firstQmIdx; if (firstQmIdx == -1) return firstStarIdx; return Math.min(firstStarIdx, firstQmIdx); } private static boolean wildcarded(String mask) { return (wildcardPos(mask) > -1); } /** * Split the mask on the file separators. Instead of the String.split() * method, we use the File.getParentFile() method to split the mask string. * This works on Windows, where the separator can be both / and \\. The * first element of the vector will be the wildcardless head of the mask. If * there is no leading wildcardless element, the first part (of the path) * will be either . or / according to the mask's type (relative/absolute * path). The vector size will be at least 1, containing . or the full mask * if the mask is entirely wildcardless. */ private static Vector splitMask(String mask) { Vector result = new Vector(); File f = new File(mask); File p = f.getParentFile(); while (wildcarded(f.getPath()) && p != null) { if (isDebugging) log.debug("Parent of file: " + f.getPath() + " is " + p.getPath()); // insert in front the substring of the mask related to this dir result.add(0, f.getName()); f = p; p = p.getParentFile(); } // insert the full path of the wildcardless mask in front result.add(0, f.getPath()); if (wildcarded(f.getPath())) { // mask does not contain a wildcardless // head // the very first part is wildcarded // we have to add a . or / as first element if (f.isAbsolute()) result.add(0, File.separator); else { // Because of later tests for symbolic links, simply adding . // here // leads to problems. // Instead, we add the canonical path of the . here String dot; try { dot = new File(".").getCanonicalPath(); } catch (IOException e) { log.debug("Cannot get canonical filename of ."); dot = new String("."); } result.add(0, dot); } } if (isDebugging) log.debug("The splitted vector is " + result.size() + " long.:"); for (int i = 0; i < result.size(); i++) { if (isDebugging) log.debug(" " + result.get(i)); } return result; } /** * Recursively traverse the directories looking for matches on each level to * the relevant part of the mask. Matched files will be deleted. Matched * directories will be deleted only if 'recursive' is true. */ private boolean delete(File node, Vector masks, boolean recursive) { if (isDebugging) log.debug(">>> " + node.getPath() + " with masks length = " + masks.size() + ": " + masks.toString()); // the query is for a single file/dir --> it will be deleted now if (masks.isEmpty()) { return deleteNode(node, recursive, "Delete "); } // handle the case where path is not a directory but something else if (!node.isDirectory()) { if (node.isFile()) { // single file // this file cannot match the rest of the query mask return true; // this is not an error, just skip } else { // wildcardless mask referred to a non-existing file/dir log.error("Path " + node.getPath() + " is not a directory!"); return false; } } // path refers to an existing dir. // Let's list its content with the appropriate mask String localMask = null; Vector restMask = (Vector) masks.clone(); if (!masks.isEmpty()) { localMask = (String) masks.firstElement(); // first element as local // mask restMask.remove(0); // the rest } boolean result = true; // will become false if at least one file removal // fails // handle special masks . and .. separately if (localMask.equals(".") || localMask.equals("..")) { // we just need to call this method again with the next mask File newNode = new File(node, localMask); if (isDebugging) log.debug("Special case of " + localMask + " --> Call delete() with " + newNode.getPath()); result = delete(newNode, restMask, recursive); } else { // meaningful mask... so list the directory and recursively traverse // directories MyLocalFilter localFilter = new MyLocalFilter(localMask); // Get files matching the localMask in the dir 'node' File[] files = node.listFiles(localFilter); if (isDebugging) log.debug("Found " + files.length + " matching files in " + node); for (int i = 0; i < files.length; i++) { // recursive call with the rest of the masks boolean succ = delete(files[i], restMask, recursive); if (!succ && isDebugging) log.debug("Failed removal of " + files[i].getPath()); result = result && succ; } } if (isDebugging) log.debug("<<< " + node.getPath()); return result; } /** * Recursively delete a file or directory. If the directory is a symbolic * link, it is not followed, but only the link will be deleted. */ private boolean deleteNode(File f, boolean recursive, String indent) { boolean result = false; if (!f.isDirectory()) { // single file log.info(indent + f); result = f.delete(); } else if (isSymbolicLink(f)) { // This directory is a symbolic link, and there's no reason for us // to // follow it, because then we might be deleting something outside of // the directory we were told to delete. // Delete the link only. log.info(indent + f + "@"); result = f.delete(); } else if (recursive) { // directory and recursive is on File[] files = f.listFiles(); for (int i = 0; i < files.length; i++) { deleteNode(files[i], recursive, indent + " "); } // finally, delete the directory itself log.info(indent + f + File.separator); result = f.delete(); } return result; } private class MyLocalFilter implements FilenameFilter { Pattern p; MyLocalFilter(String filemask) { String pattern; // convert file mask pattern to regular expression if (filemask != null) { String p1 = filemask.replaceAll("\\.", "\\\\."); String p2 = p1.replaceAll("\\*", ".*"); String p3 = p2.replaceAll("\\?", "."); // System.out.println("pattern conversion: [" + p3 + "] = [" + // p1 + "] -> [" + p2 + "]"); p = Pattern.compile(p3); } else p = null; } public boolean accept(File dir, String name) { if (p != null) { Matcher m = p.matcher(name); return m.matches(); } else return true; } } /** * Test if a File is a symbolic link. It returns true if the file is a * symbolic link, false otherwise. Exception: if the symlink points to * itself, it returns false. Sorry. How does it work: it compares the * canonical path and the absolute path of the file. The former gives the * referred file of a link and thus differs from the absolute path of the * link itself. */ private static boolean isSymbolicLink(File f) { if (f == null) return false; if (!f.exists()) return false; // special case: path ends with .., which can never be a symlink, right? // Note: symlink/.. refers to the directory containing the symlink and // not the link itself // special case: path ends with ., it is the same // Note: symlink/. refers to the pointed directory and not the link // itself // They are handled here because they would result in true in later // tests. if (f.getName().equals("..") || f.getName().equals(".")) return false; // to see if this file is actually a symbolic link to a directory, // we want to get its canonical path - that is, we follow the link to // the file it's actually linked to File canf; try { canf = f.getCanonicalFile(); } catch (IOException e) { log.error("Cannot get canonical filename of file " + f.getPath()); return true; } // we need to get the absolute path // Unfortunately File.getAbsolutePath() does not eliminate . and .. // thus the equality test fails for paths containing them. // Let's do some magic with the parent dir name and get the absolute // path this way. File absf; File parent = f.getParentFile(); if (parent == null) { // no problem, we have a single (relative) file name absf = f.getAbsoluteFile(); } else { // eliminate . and .. from the parent path, using getCanonicalFile() try { parent = parent.getCanonicalFile(); } catch (IOException e) { log.error("Cannot get canonical filename of file " + parent.getPath()); } // recreate the absolute filename // Note: if f's name is .., this would not be eliminated here. See // pre-test above absf = new File(parent, f.getName()); } if (isDebugging) log.debug( "File " + f.getPath() + "\nCanonical = " + canf.getPath() + "\nAbsolute = " + absf.getPath()); // a symbolic link has a different canonical path than its actual path, // unless it's a link to itself return (!canf.equals(absf)); } }