/* * Copyright (c) 2005, Rob Gordon. */ package org.oddjob.io; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.apache.commons.io.IOCase; import org.apache.commons.io.filefilter.WildcardFileFilter; /** * Utility class for wild-card file matching. Note that the File objects * returned are all in the canonical file format. */ public class WildcardSpec { private final File file; /** * Create a new instance with a String file path. * * @param spec */ public WildcardSpec(String spec) { this(new File(spec)); } /** * Create a new instance with a file. * * @param file */ public WildcardSpec(File file) { if (file == null) { throw new NullPointerException("No file."); } this.file = file; } /** * Is the file specification a wild card specification. * * @return */ public boolean isWildcardSpec() { return !noWildcard(file.getPath()); } /** * Find all files matching the specialisation. * * @return * @throws IOException */ public File[] findFiles() throws IOException { DirectorySplit split = new DirectorySplit(file); return findFiles(split); } /** * Recursively find file in the path. * * @param split The path split up into wildcard sections. * * @return * @throws IOException */ protected File[] findFiles(DirectorySplit split) throws IOException { Set<File> results = new TreeSet<File>(); File currentFile = split.currentFile(); File currentParent = currentFile.getAbsoluteFile().getParentFile(); // currentParent will be null when file is "/" // noWildCard should catch this too - lets be certain. if (currentParent == null) { results.add(currentFile.getCanonicalFile()); } else if (currentParent.exists()) { // Find all files matching the current paths name. This // may or may not be a wild card match. List<File> matching = new ArrayList<>(); possiblyRecursiveMatch(currentParent, currentFile.getName(), matching); // for each match, either move down the tree and continue // to match or add the result if we are at the bottom of // the path. for (File match: matching) { if (split.isBottom()) { results.add(match.getCanonicalFile()); } else { if (match.isDirectory()) { // recursive call. File[] more = findFiles( split.next(match)); results.addAll(Arrays.asList(more)); } else { // Ignore if the match is not a directory. } } } } return results.toArray(new File[results.size()]); } /** * Splits a directory into what is above and below the wild cards. * <p> * Given a file such as <code>a/b/???/x/y/???</code> this will create a * split that is: * <ul> * <li>a/b/???/x/y/???</li> * <li>a/b/???</li> * </ul> * This is stored as * <ul> * <li>x/y/???</li> * <li>a/b/???</li> * </ul> * <p> * */ static class DirectorySplit { final private LinkedList<File> split; /** * Create a new instance. * * @param file */ DirectorySplit(File file) { if (file == null) { throw new NullPointerException(); } split = new LinkedList<File>(); File current = file; File parent = current.getParentFile(); while (true) { if (parent == null || noWildcard(parent.getPath())) { split.add(current); break; } File below = new File(current.getName()); while (true){ current = parent; parent = current.getParentFile(); if (parent == null || !noWildcard(current.getName())) { break; } below = new File(current.getName(), below.getPath()); } split.add(below); } } /** * Create a new instance for {@link #next(String)}. * * @param previous * @param name * @throws IOException */ private DirectorySplit(DirectorySplit previous, File newParent) throws IOException { split = new LinkedList<File>(previous.split); split.removeLast().getParentFile(); // remove the last; String nextDown = split.removeLast().getPath(); // replace the current; split.add(new File(newParent, nextDown)); } File getParentFile() throws IOException { File parent = split.getLast().getParentFile(); return parent; } File currentFile() { return split.getLast(); } String getName() { return split.getLast().getName(); } boolean isBottom() { return split.size() == 1; } int getSize() { return split.size(); } DirectorySplit next(File newParent) throws IOException { if (split.size() == 1) { return null; } DirectorySplit next = new DirectorySplit(this, newParent); return next; } } static boolean noWildcard(String text) { return text.indexOf('*') < 0 && text.indexOf('?') < 0; } public static void possiblyRecursiveMatch(final File from, final String namespec, final List<File> results) { String singleWildcards = namespec.replace("**", "*"); File[] matching = from.listFiles( (FileFilter) new WildcardFileFilter(singleWildcards, IOCase.SYSTEM)); if (matching == null) { throw new IllegalArgumentException("Can't list files for directory " + from + " and name spec " + singleWildcards); } results.addAll(Arrays.asList(matching)); if (singleWildcards.equals(namespec)) { return; } if ("*".equals(singleWildcards) && results.size() == matching.length) { results.add(0, from); } for (File match : matching) { if (match.isDirectory()) { possiblyRecursiveMatch(match, namespec, results); } } } }