Created
March 7, 2013 10:47
-
-
Save hfs/5107205 to your computer and use it in GitHub Desktop.
Ant regexp mapper to mass rename/move files, reading its regular expressions from a file. Public Domain code, do with it whatever you like.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.hfs.anttasks; | |
import java.io.BufferedReader; | |
import java.io.File; | |
import java.io.FileNotFoundException; | |
import java.io.FileReader; | |
import java.io.IOException; | |
import org.apache.tools.ant.BuildException; | |
import org.apache.tools.ant.util.CompositeMapper; | |
import org.apache.tools.ant.util.FileNameMapper; | |
import org.apache.tools.ant.util.RegexpPatternMapper; | |
/** | |
* <p> | |
* A <code>FileNameMapper</code> that maps using a collection of regular | |
* expressions that are read from a file. | |
* </p> | |
* <p> | |
* The regular expressions are read from a text file. Each line contains two | |
* expressions with the <em>from</em> and the <em>to</em> pattern. A delimiter | |
* character is defined as the first character of the line and must be used to | |
* separate the two expressions and finish the second. Each file name to be | |
* mapped is matched against all <em>from</em> expressions and for all matches | |
* the <em>to</em> expressions are evaluated and returned. | |
* </p> | |
* <p> | |
* The <em>to</em> expression defines the complete target file name including | |
* the directory part, so if any of that should be preserved, work with grouping | |
* <code>(...)</code> and backreferences <code>\1, \2, </code>... This is the | |
* same behaviour as in Ant's {@code <regexpmapper>}. | |
* </p> | |
* <p> | |
* A backslash "<code>\</code>" can be used to escape the delimiter character. | |
* All other backslashes are used in the regular expressions and don't need to | |
* be escaped themselves. | |
* </p> | |
* <p> | |
* An example definition file could be: | |
* <pre> | |
* {@code | |
* ,(.*)from\.(.*),\1to\,name\2, | |
* } | |
* </pre> | |
* Here "<code>,</code>" is the delimiter. | |
* </p> | |
* <p> | |
* Internally a {@code CompositeMapper} with {@code RegexpPatternMapper}s which | |
* are initialized from the file's contents are used. | |
* </p> | |
* <p> | |
* To use the mapper efficiently typedef it to a name: | |
* <pre>{@code | |
* <typedef name="regexpfilemapper" | |
* classname="com.github.hfs.anttasks.AntRegexpFileMapper"> | |
* <classpath .../> | |
* </typedef> | |
* }</pre> | |
* It can then be used like the other mappers: | |
* <pre>{@code | |
* <copy todir="to"> | |
* <fileset dir="from"/> | |
* <regexpfilemapper file="regexp.properties" handleDirSep="true"/> | |
* </copy> | |
* }</pre> | |
* </p> | |
* | |
* @author <a href="mailto:[email protected]">Hermann Schwarting</a> | |
*/ | |
public class AntRegexpFileMapper implements FileNameMapper { | |
/** Escape character in pattern definitions */ | |
private static final char ESCAPE = '\\'; | |
/** Holds all used RegexpPatternMappers */ | |
private final CompositeMapper mappers = new CompositeMapper(); | |
/** Fail if the file does not exist or cannot be read. Default: {@value} */ | |
boolean mandatory = true; | |
/** The file to read expressions from */ | |
File file; | |
/** | |
* Expressions should be matched case sensitive. Default: {@value} . Passed | |
* through to all RegexpPatternMappers | |
*/ | |
boolean caseSensitive = true; | |
/** | |
* Treat a "\" character in a filename as a "/" for the purposes of | |
* matching. Default: {@value} . Passed through to all RegexpPatternMappers. | |
*/ | |
boolean handleDirSep = false; | |
/** The input file has been read and all mappers have been setup. */ | |
boolean setupComplete = false; | |
@Override | |
public String[] mapFileName(String sourceFileName) { | |
if (! setupComplete) { | |
validate(); | |
readRegexpFile(); | |
setupComplete = true; | |
} | |
return mappers.mapFileName(sourceFileName); | |
} | |
/** The file to read expressions from */ | |
public void setFile(File regexpFile) { | |
this.file = regexpFile; | |
} | |
/** Fail if the file does not exist or cannot be read. */ | |
public void setMandatory(boolean mandatory) { | |
this.mandatory = mandatory; | |
} | |
/** Expressions should be matched case sensitive */ | |
public void setCaseSensitive(boolean caseSensitive) { | |
this.caseSensitive = caseSensitive; | |
} | |
/** | |
* Treat a "\" character in a filename as a "/" for the purposes of | |
* matching. | |
*/ | |
public void setHandleDirSep(boolean handleDirSep) { | |
this.handleDirSep = handleDirSep; | |
} | |
/** | |
* Read the file with regexp pattern definitions and create the | |
* {@code RegexpPatternMapper}s that will do the actual mapping. | |
* | |
* @throws BuildException if {@code mandatory} is {@code true} and the file | |
* does not exist or an IO error happens. | |
*/ | |
private void readRegexpFile() throws BuildException { | |
try { | |
BufferedReader reader = new BufferedReader(new FileReader(this.file)); | |
String line; | |
while ((line = reader.readLine()) != null) { | |
if (line.isEmpty()) { | |
continue; | |
} | |
ReplaceRegexps fromTo = parseRegexps(line); | |
RegexpPatternMapper mapper = new RegexpPatternMapper(); | |
mapper.setFrom(fromTo.getFrom()); | |
mapper.setTo(fromTo.getTo()); | |
mapper.setCaseSensitive(caseSensitive); | |
mapper.setHandleDirSep(handleDirSep); | |
mappers.add(mapper); | |
} | |
} catch (FileNotFoundException e) { | |
if (mandatory) { | |
throw new BuildException(e); | |
} | |
} catch (IOException e) { | |
if (mandatory) { | |
throw new BuildException(e); | |
} | |
} | |
} | |
/** | |
* Split one line of input into the <em>from</em> and the <em>to</em> | |
* pattern. The lines must be of the form "|from|to|" where "|" is a | |
* user-defined delimiter character. | |
* | |
* @param line One non-empty line of input | |
* @return the split <em>from</em> and <em>to</em> expressions | |
* @throws BuildException if the line is not well-formed | |
*/ | |
private ReplaceRegexps parseRegexps(String line) { | |
StringBuffer from = new StringBuffer(); | |
StringBuffer to = new StringBuffer(); | |
boolean escape = false; | |
// 0 = from, 1 = to, 2 = trailing | |
int part = 0; | |
int i; | |
char delimiter = line.charAt(0); | |
if (Character.isWhitespace(delimiter) || delimiter == ESCAPE) { | |
throw new BuildException("Whitespace or '" + ESCAPE + "' are not " + | |
"allowed as delimiter in line '" + line + "'"); | |
} | |
for (i = 1; i < line.length(); ++i) { | |
boolean add = true; | |
char c = line.charAt(i); | |
if (escape) { | |
escape = false; | |
} else if (c == ESCAPE) { | |
// Escape mode "on" if the next is the delimiter | |
if (i < line.length() - 1 && line.charAt(i + 1) == delimiter) { | |
escape = true; | |
add = false; // Swallow the escape char | |
} | |
} else if (c == delimiter) { | |
++part; | |
add = false; | |
} | |
if (add) { | |
if (part == 0) { | |
from.append(c); | |
} else if (part == 1) { | |
to.append(c); | |
} else { | |
throw new BuildException("Trailing characters in regexp " + | |
"definition in line '" + line + "'"); | |
} | |
} | |
} | |
if (part < 2) { | |
throw new BuildException("Incomplete mapping definition in line '" + | |
line + "'"); | |
} | |
return new ReplaceRegexps(from.toString(), to.toString()); | |
} | |
/** | |
* Check if all mandatory attributes have been set. | |
* | |
* @throws BuildException if a mandatory attribute is not set | |
*/ | |
private void validate() throws BuildException { | |
if (mandatory && file == null) { | |
throw new BuildException("Mandatory attribute 'file' not set"); | |
} | |
} | |
/** Ignored */ | |
@Override | |
public void setFrom(String ignore) { | |
} | |
/** Ignored */ | |
@Override | |
public void setTo(String ignore) { | |
} | |
/** | |
* Replacement expressions <em>from</em> and <em>to</em>. | |
*/ | |
private class ReplaceRegexps { | |
private final String from; | |
private final String to; | |
public ReplaceRegexps(String from, String to) { | |
this.from = from; | |
this.to = to; | |
} | |
public String getFrom() { | |
return from; | |
} | |
public String getTo() { | |
return to; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment