| @@ -182,6 +182,7 @@ Jesse Glick | |||||
| Jesse Stockall | Jesse Stockall | ||||
| Jim Allers | Jim Allers | ||||
| Joerg Wassmer | Joerg Wassmer | ||||
| Joel Tucci | |||||
| Joey Richey | Joey Richey | ||||
| Johann Herunter | Johann Herunter | ||||
| John Elion | John Elion | ||||
| @@ -299,6 +300,7 @@ Pierre Delisle | |||||
| Pierre Dittgen | Pierre Dittgen | ||||
| riasol | riasol | ||||
| R Handerson | R Handerson | ||||
| Ralf Hergert | |||||
| Rami Ojares | Rami Ojares | ||||
| Randy Watler | Randy Watler | ||||
| Raphael Pierquin | Raphael Pierquin | ||||
| @@ -173,9 +173,6 @@ Note: This document was updated in the context of releasing Ant 1.9.3. | |||||
| days pass and there are no major problems, a wider announcement is | days pass and there are no major problems, a wider announcement is | ||||
| made (ant website, announce@apache.org, etc). | made (ant website, announce@apache.org, etc). | ||||
| Announce beta releases at freecode.com (Stefan Bodewig is the | |||||
| owner of Ant's project entry - bug him ;-). | |||||
| 17. As problems in the beta are discovered, there may be a need for | 17. As problems in the beta are discovered, there may be a need for | ||||
| one or more subsequent betas. The release manager makes this | one or more subsequent betas. The release manager makes this | ||||
| call. Each time, the versions are updated and the above process is | call. Each time, the versions are updated and the above process is | ||||
| @@ -256,9 +253,6 @@ Note: This document was updated in the context of releasing Ant 1.9.3. | |||||
| Apache mailing lists that should get the announcements: | Apache mailing lists that should get the announcements: | ||||
| announce@apache.org, dev@ant and user@ant. | announce@apache.org, dev@ant and user@ant. | ||||
| Announce release at freecode.com | |||||
| (Stefan Bodewig is the owner of Ant's project entry - bug him ;-). | |||||
| 25. Add a new release tag to doap_Ant.rdf in Ant's site. | 25. Add a new release tag to doap_Ant.rdf in Ant's site. | ||||
| 26. You can now reacquaint yourself with your family and friends. | 26. You can now reacquaint yourself with your family and friends. | ||||
| @@ -4,9 +4,29 @@ Changes from Ant 1.9.4 TO Ant 1.9.5 | |||||
| Changes that could break older environments: | Changes that could break older environments: | ||||
| ------------------------------------------- | ------------------------------------------- | ||||
| * The ReplaceTokens filter can now use token-separators longer than | |||||
| one character. This means it can be used to replace mustache-style | |||||
| {{patterns}} and similar templates. This is going to break code | |||||
| that invokes the setters on ReplaceTokens via the Java API as their | |||||
| parameters have been changed from char to String. It may also | |||||
| break build files that specified multi character tokens and relied | |||||
| on Ant silently ignoring all but the first character. | |||||
| Bugzilla Report 56584 | |||||
| Fixed bugs: | Fixed bugs: | ||||
| ----------- | ----------- | ||||
| * TarArchiveInputStream failed to read archives with empty gid/uid | |||||
| fields. | |||||
| Bugzilla Report 56641 | |||||
| * TarArchiveInputStream could throw IOException when reading PAX | |||||
| headers from a "slow" InputStream. | |||||
| * XMLJunitResultFormatter could throw NullPointerException if Java | |||||
| cannot determine the local hostname. | |||||
| Bugzilla Report 56593 | |||||
| Other changes: | Other changes: | ||||
| -------------- | -------------- | ||||
| @@ -751,6 +751,10 @@ | |||||
| <first>Joerg</first> | <first>Joerg</first> | ||||
| <last>Wassmer</last> | <last>Wassmer</last> | ||||
| </name> | </name> | ||||
| <name> | |||||
| <first>Joel</first> | |||||
| <last>Tucci</last> | |||||
| </name> | |||||
| <name> | <name> | ||||
| <first>Joey</first> | <first>Joey</first> | ||||
| <last>Richey</last> | <last>Richey</last> | ||||
| @@ -1209,6 +1213,10 @@ | |||||
| <first>R</first> | <first>R</first> | ||||
| <last>Handerson</last> | <last>Handerson</last> | ||||
| </name> | </name> | ||||
| <name> | |||||
| <first>Ralf</first> | |||||
| <last>Hergert</last> | |||||
| </name> | |||||
| <name> | <name> | ||||
| <first>Rami</first> | <first>Rami</first> | ||||
| <last>Ojares</last> | <last>Ojares</last> | ||||
| @@ -551,14 +551,14 @@ user defined values. | |||||
| <tr> | <tr> | ||||
| <td vAlign=top>tokenchar</td> | <td vAlign=top>tokenchar</td> | ||||
| <td vAlign=top>begintoken</td> | <td vAlign=top>begintoken</td> | ||||
| <td vAlign=top>Character marking the | |||||
| <td vAlign=top>String marking the | |||||
| beginning of a token. Defaults to @</td> | beginning of a token. Defaults to @</td> | ||||
| <td vAlign=top align="center">No</td> | <td vAlign=top align="center">No</td> | ||||
| </tr> | </tr> | ||||
| <tr> | <tr> | ||||
| <td vAlign=top>tokenchar</td> | <td vAlign=top>tokenchar</td> | ||||
| <td vAlign=top>endtoken</td> | <td vAlign=top>endtoken</td> | ||||
| <td vAlign=top>Character marking the | |||||
| <td vAlign=top>String marking the | |||||
| end of a token. Defaults to @</td> | end of a token. Defaults to @</td> | ||||
| <td vAlign=top align="center">No</td> | <td vAlign=top align="center">No</td> | ||||
| </tr> | </tr> | ||||
| @@ -626,6 +626,31 @@ Convenience method: | |||||
| </loadfile> | </loadfile> | ||||
| </pre></blockquote> | </pre></blockquote> | ||||
| This replaces occurrences of the string {{DATE}} in the data | |||||
| with today's date and stores it in the property ${src.file.replaced}. | |||||
| <blockquote><pre> | |||||
| <loadfile srcfile="${src.file}" property="${src.file.replaced}"> | |||||
| <filterchain> | |||||
| <filterreader classname="org.apache.tools.ant.filters.ReplaceTokens"> | |||||
| <param type="tokenchar" name="begintoken" value="{{"/> | |||||
| <param type="tokenchar" name="endtoken" value="}}"/> | |||||
| </filterreader> | |||||
| </filterchain> | |||||
| </loadfile> | |||||
| </pre></blockquote> | |||||
| Convenience method: | |||||
| <blockquote><pre> | |||||
| <tstamp/> | |||||
| <loadfile srcfile="${src.file}" property="${src.file.replaced}"> | |||||
| <filterchain> | |||||
| <replacetokens begintoken="{{" endtoken="}}"> | |||||
| <token key="DATE" value="${TODAY}"/> | |||||
| </replacetokens> | |||||
| </filterchain> | |||||
| </loadfile> | |||||
| </pre></blockquote> | |||||
| This will treat each properties file entry in sample.properties as a token/key pair : | This will treat each properties file entry in sample.properties as a token/key pair : | ||||
| <blockquote><pre> | <blockquote><pre> | ||||
| <loadfile srcfile="${src.file}" property="${src.file.replaced}"> | <loadfile srcfile="${src.file}" property="${src.file.replaced}"> | ||||
| @@ -100,6 +100,39 @@ | |||||
| </copy> | </copy> | ||||
| </target> | </target> | ||||
| <target name="testReplaceTokensDoubleEncoded" depends="setUp"> | |||||
| <copy todir="${output}"> | |||||
| <fileset dir="input" includes="replacetokens.double.test" /> | |||||
| <filterchain> | |||||
| <replacetokens> | |||||
| <token key="foo" value=""/> | |||||
| </replacetokens> | |||||
| </filterchain> | |||||
| </copy> | |||||
| </target> | |||||
| <target name="testReplaceTokensDoubleEncodedToSimple" depends="setUp"> | |||||
| <copy todir="${output}"> | |||||
| <fileset dir="input" includes="replacetokens.double.test" /> | |||||
| <filterchain> | |||||
| <replacetokens begintoken="@@" endtoken="@@"> | |||||
| <token key="foo" value=""/> | |||||
| </replacetokens> | |||||
| </filterchain> | |||||
| </copy> | |||||
| </target> | |||||
| <target name="testReplaceTokensMustacheStyle" depends="setUp"> | |||||
| <copy todir="${output}"> | |||||
| <fileset dir="input" includes="replacetokens.mustache.test" /> | |||||
| <filterchain> | |||||
| <replacetokens begintoken="{{" endtoken="}}"> | |||||
| <token key="foo" value=""/> | |||||
| </replacetokens> | |||||
| </filterchain> | |||||
| </copy> | |||||
| </target> | |||||
| <target name="testNoAddNewLine" depends="setUp"> | <target name="testNoAddNewLine" depends="setUp"> | ||||
| <concat destfile="${output}/nonl">This has no new lines</concat> | <concat destfile="${output}/nonl">This has no new lines</concat> | ||||
| <copy file="${output}/nonl" tofile="${output}/nonl-copyfilter"> | <copy file="${output}/nonl" tofile="${output}/nonl-copyfilter"> | ||||
| @@ -0,0 +1,2 @@ | |||||
| 1@@2 | |||||
| 3 | |||||
| @@ -0,0 +1,2 @@ | |||||
| 1@@foo@@2 | |||||
| 3 | |||||
| @@ -0,0 +1,2 @@ | |||||
| 1{{foo}}2 | |||||
| 3 | |||||
| @@ -24,7 +24,9 @@ import java.io.Reader; | |||||
| import java.util.Enumeration; | import java.util.Enumeration; | ||||
| import java.util.Hashtable; | import java.util.Hashtable; | ||||
| import java.util.Properties; | import java.util.Properties; | ||||
| import org.apache.tools.ant.BuildException; | |||||
| import java.util.SortedMap; | |||||
| import java.util.TreeMap; | |||||
| import org.apache.tools.ant.types.Parameter; | import org.apache.tools.ant.types.Parameter; | ||||
| import org.apache.tools.ant.types.Resource; | import org.apache.tools.ant.types.Resource; | ||||
| import org.apache.tools.ant.types.resources.FileResource; | import org.apache.tools.ant.types.resources.FileResource; | ||||
| @@ -52,13 +54,19 @@ public final class ReplaceTokens | |||||
| extends BaseParamFilterReader | extends BaseParamFilterReader | ||||
| implements ChainableReader { | implements ChainableReader { | ||||
| /** Default "begin token" character. */ | /** Default "begin token" character. */ | ||||
| private static final char DEFAULT_BEGIN_TOKEN = '@'; | |||||
| private static final String DEFAULT_BEGIN_TOKEN = "@"; | |||||
| /** Default "end token" character. */ | /** Default "end token" character. */ | ||||
| private static final char DEFAULT_END_TOKEN = '@'; | |||||
| private static final String DEFAULT_END_TOKEN = "@"; | |||||
| /** Hashtable to holds the original replacee-replacer pairs (String to String). */ | |||||
| private Hashtable<String, String> hash = new Hashtable<String, String>(); | |||||
| /** Data to be used before reading from stream again */ | |||||
| private String queuedData = null; | |||||
| /** This map holds the "resolved" tokens (begin- and end-tokens are added to make searching simpler) */ | |||||
| private final TreeMap<String, String> resolvedTokens = new TreeMap<String, String>(); | |||||
| private boolean resolvedTokensBuilt = false; | |||||
| /** Used for comparisons and lookup into the resolvedTokens map. */ | |||||
| private String readBuffer = ""; | |||||
| /** replacement test from a token */ | /** replacement test from a token */ | ||||
| private String replaceData = null; | private String replaceData = null; | ||||
| @@ -66,26 +74,18 @@ public final class ReplaceTokens | |||||
| /** Index into replacement data */ | /** Index into replacement data */ | ||||
| private int replaceIndex = -1; | private int replaceIndex = -1; | ||||
| /** Index into queue data */ | |||||
| private int queueIndex = -1; | |||||
| /** Hashtable to hold the replacee-replacer pairs (String to String). */ | |||||
| private Hashtable<String, String> hash = new Hashtable<String, String>(); | |||||
| /** Character marking the beginning of a token. */ | /** Character marking the beginning of a token. */ | ||||
| private char beginToken = DEFAULT_BEGIN_TOKEN; | |||||
| private String beginToken = DEFAULT_BEGIN_TOKEN; | |||||
| /** Character marking the end of a token. */ | /** Character marking the end of a token. */ | ||||
| private char endToken = DEFAULT_END_TOKEN; | |||||
| private String endToken = DEFAULT_END_TOKEN; | |||||
| /** | /** | ||||
| * Constructor for "dummy" instances. | * Constructor for "dummy" instances. | ||||
| * | * | ||||
| * @see BaseFilterReader#BaseFilterReader() | * @see BaseFilterReader#BaseFilterReader() | ||||
| */ | */ | ||||
| public ReplaceTokens() { | |||||
| super(); | |||||
| } | |||||
| public ReplaceTokens() {} | |||||
| /** | /** | ||||
| * Creates a new filtered reader. | * Creates a new filtered reader. | ||||
| @@ -97,18 +97,6 @@ public final class ReplaceTokens | |||||
| super(in); | super(in); | ||||
| } | } | ||||
| private int getNextChar() throws IOException { | |||||
| if (queueIndex != -1) { | |||||
| final int ch = queuedData.charAt(queueIndex++); | |||||
| if (queueIndex >= queuedData.length()) { | |||||
| queueIndex = -1; | |||||
| } | |||||
| return ch; | |||||
| } | |||||
| return in.read(); | |||||
| } | |||||
| /** | /** | ||||
| * Returns the next character in the filtered stream, replacing tokens | * Returns the next character in the filtered stream, replacing tokens | ||||
| * from the original stream. | * from the original stream. | ||||
| @@ -125,63 +113,66 @@ public final class ReplaceTokens | |||||
| setInitialized(true); | setInitialized(true); | ||||
| } | } | ||||
| if (replaceIndex != -1) { | |||||
| final int ch = replaceData.charAt(replaceIndex++); | |||||
| if (replaceIndex >= replaceData.length()) { | |||||
| replaceIndex = -1; | |||||
| if (!resolvedTokensBuilt) { | |||||
| // build the resolved tokens tree map. | |||||
| for (String key : hash.keySet()) { | |||||
| resolvedTokens.put(beginToken + key + endToken, hash.get(key)); | |||||
| } | } | ||||
| return ch; | |||||
| resolvedTokensBuilt = true; | |||||
| } | } | ||||
| int ch = getNextChar(); | |||||
| if (ch == beginToken) { | |||||
| final StringBuffer key = new StringBuffer(""); | |||||
| do { | |||||
| ch = getNextChar(); | |||||
| if (ch != -1) { | |||||
| key.append((char) ch); | |||||
| } else { | |||||
| break; | |||||
| } | |||||
| } while (ch != endToken); | |||||
| if (ch == -1) { | |||||
| if (queuedData == null || queueIndex == -1) { | |||||
| queuedData = key.toString(); | |||||
| } else { | |||||
| queuedData | |||||
| = key.toString() + queuedData.substring(queueIndex); | |||||
| } | |||||
| if (queuedData.length() > 0) { | |||||
| queueIndex = 0; | |||||
| } else { | |||||
| queueIndex = -1; | |||||
| } | |||||
| return beginToken; | |||||
| // are we currently serving replace data? | |||||
| if (replaceData != null) { | |||||
| if (replaceIndex < replaceData.length()) { | |||||
| return replaceData.charAt(replaceIndex++); | |||||
| } else { | } else { | ||||
| key.setLength(key.length() - 1); | |||||
| replaceData = null; | |||||
| } | |||||
| } | |||||
| final String replaceWith = (String) hash.get(key.toString()); | |||||
| if (replaceWith != null) { | |||||
| if (replaceWith.length() > 0) { | |||||
| replaceData = replaceWith; | |||||
| replaceIndex = 0; | |||||
| } | |||||
| return read(); | |||||
| // is the read buffer empty? | |||||
| if (readBuffer.length() == 0) { | |||||
| int next = in.read(); | |||||
| if (next == -1) { | |||||
| return next; // end of stream. all buffers empty. | |||||
| } | |||||
| readBuffer += (char)next; | |||||
| } | |||||
| for (;;) { | |||||
| // get the closest tokens | |||||
| SortedMap<String,String> possibleTokens = resolvedTokens.tailMap(readBuffer); | |||||
| if (possibleTokens.isEmpty() || !possibleTokens.firstKey().startsWith(readBuffer)) { // if there is none, then deliver the first char from the buffer. | |||||
| return getFirstCharacterFromReadBuffer(); | |||||
| } else if (readBuffer.equals(possibleTokens.firstKey())) { // there exists a nearest token - is it an exact match? | |||||
| // we have found a token. prepare the replaceData buffer. | |||||
| replaceData = resolvedTokens.get(readBuffer); | |||||
| replaceIndex = 0; | |||||
| readBuffer = ""; // destroy the readBuffer - it's contents are being replaced entirely. | |||||
| // get the first character via recursive call. | |||||
| return read(); | |||||
| } else { // nearest token is not matching exactly - read one character more. | |||||
| int next = in.read(); | |||||
| if (next != -1) { | |||||
| readBuffer += (char)next; | |||||
| } else { | } else { | ||||
| String newData = key.toString() + endToken; | |||||
| if (queuedData == null || queueIndex == -1) { | |||||
| queuedData = newData; | |||||
| } else { | |||||
| queuedData = newData + queuedData.substring(queueIndex); | |||||
| } | |||||
| queueIndex = 0; | |||||
| return beginToken; | |||||
| return getFirstCharacterFromReadBuffer(); // end of stream. deliver remaining characters from buffer. | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return ch; | |||||
| } | |||||
| /** | |||||
| * @return the first character from the read buffer or -1 if read buffer is empty. | |||||
| */ | |||||
| private int getFirstCharacterFromReadBuffer() { | |||||
| if (readBuffer.length() > 0) { | |||||
| int chr = readBuffer.charAt(0); | |||||
| readBuffer = readBuffer.substring(1); | |||||
| return chr; | |||||
| } else { | |||||
| return -1; | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -189,7 +180,7 @@ public final class ReplaceTokens | |||||
| * | * | ||||
| * @param beginToken the character used to denote the beginning of a token | * @param beginToken the character used to denote the beginning of a token | ||||
| */ | */ | ||||
| public void setBeginToken(final char beginToken) { | |||||
| public void setBeginToken(final String beginToken) { | |||||
| this.beginToken = beginToken; | this.beginToken = beginToken; | ||||
| } | } | ||||
| @@ -198,7 +189,7 @@ public final class ReplaceTokens | |||||
| * | * | ||||
| * @return the character used to denote the beginning of a token | * @return the character used to denote the beginning of a token | ||||
| */ | */ | ||||
| private char getBeginToken() { | |||||
| private String getBeginToken() { | |||||
| return beginToken; | return beginToken; | ||||
| } | } | ||||
| @@ -207,7 +198,7 @@ public final class ReplaceTokens | |||||
| * | * | ||||
| * @param endToken the character used to denote the end of a token | * @param endToken the character used to denote the end of a token | ||||
| */ | */ | ||||
| public void setEndToken(final char endToken) { | |||||
| public void setEndToken(final String endToken) { | |||||
| this.endToken = endToken; | this.endToken = endToken; | ||||
| } | } | ||||
| @@ -216,7 +207,7 @@ public final class ReplaceTokens | |||||
| * | * | ||||
| * @return the character used to denote the end of a token | * @return the character used to denote the end of a token | ||||
| */ | */ | ||||
| private char getEndToken() { | |||||
| private String getEndToken() { | |||||
| return endToken; | return endToken; | ||||
| } | } | ||||
| @@ -238,18 +229,19 @@ public final class ReplaceTokens | |||||
| */ | */ | ||||
| public void addConfiguredToken(final Token token) { | public void addConfiguredToken(final Token token) { | ||||
| hash.put(token.getKey(), token.getValue()); | hash.put(token.getKey(), token.getValue()); | ||||
| resolvedTokensBuilt = false; // invalidate to build them again if they have been built already. | |||||
| } | } | ||||
| /** | /** | ||||
| * Returns properties from a specified properties file. | * Returns properties from a specified properties file. | ||||
| * | * | ||||
| * @param fileName The file to load properties from. | |||||
| * @param resource The resource to load properties from. | |||||
| */ | */ | ||||
| private Properties getProperties(Resource r) { | |||||
| private Properties getProperties(Resource resource) { | |||||
| InputStream in = null; | InputStream in = null; | ||||
| Properties props = new Properties(); | Properties props = new Properties(); | ||||
| try { | try { | ||||
| in = r.getInputStream(); | |||||
| in = resource.getInputStream(); | |||||
| props.load(in); | props.load(in); | ||||
| } catch (IOException ioe) { | } catch (IOException ioe) { | ||||
| ioe.printStackTrace(); | ioe.printStackTrace(); | ||||
| @@ -305,32 +297,23 @@ public final class ReplaceTokens | |||||
| private void initialize() { | private void initialize() { | ||||
| Parameter[] params = getParameters(); | Parameter[] params = getParameters(); | ||||
| if (params != null) { | if (params != null) { | ||||
| for (int i = 0; i < params.length; i++) { | |||||
| if (params[i] != null) { | |||||
| final String type = params[i].getType(); | |||||
| for (Parameter param : params) { | |||||
| if (param != null) { | |||||
| final String type = param.getType(); | |||||
| if ("tokenchar".equals(type)) { | if ("tokenchar".equals(type)) { | ||||
| final String name = params[i].getName(); | |||||
| String value = params[i].getValue(); | |||||
| final String name = param.getName(); | |||||
| if ("begintoken".equals(name)) { | if ("begintoken".equals(name)) { | ||||
| if (value.length() == 0) { | |||||
| throw new BuildException("Begin token cannot " | |||||
| + "be empty"); | |||||
| } | |||||
| beginToken = params[i].getValue().charAt(0); | |||||
| beginToken = param.getValue(); | |||||
| } else if ("endtoken".equals(name)) { | } else if ("endtoken".equals(name)) { | ||||
| if (value.length() == 0) { | |||||
| throw new BuildException("End token cannot " | |||||
| + "be empty"); | |||||
| } | |||||
| endToken = params[i].getValue().charAt(0); | |||||
| endToken = param.getValue(); | |||||
| } | } | ||||
| } else if ("token".equals(type)) { | } else if ("token".equals(type)) { | ||||
| final String name = params[i].getName(); | |||||
| final String value = params[i].getValue(); | |||||
| final String name = param.getName(); | |||||
| final String value = param.getValue(); | |||||
| hash.put(name, value); | hash.put(name, value); | ||||
| } else if ("propertiesfile".equals(type)) { | } else if ("propertiesfile".equals(type)) { | ||||
| makeTokensFromProperties( | makeTokensFromProperties( | ||||
| new FileResource(new File(params[i].getValue()))); | |||||
| new FileResource(new File(param.getValue()))); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -161,11 +161,15 @@ public class XMLJUnitResultFormatter implements JUnitResultFormatter, XMLConstan | |||||
| * @return the name of the local host, or "localhost" if we cannot work it out | * @return the name of the local host, or "localhost" if we cannot work it out | ||||
| */ | */ | ||||
| private String getHostname() { | private String getHostname() { | ||||
| String hostname = "localhost"; | |||||
| try { | try { | ||||
| return InetAddress.getLocalHost().getHostName(); | |||||
| InetAddress localHost = InetAddress.getLocalHost(); | |||||
| if (localHost != null) { | |||||
| hostname = localHost.getHostName(); | |||||
| } | |||||
| } catch (UnknownHostException e) { | } catch (UnknownHostException e) { | ||||
| return "localhost"; | |||||
| } | } | ||||
| return hostname; | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -431,18 +431,22 @@ public class TarInputStream extends FilterInputStream { | |||||
| if (ch == '='){ // end of keyword | if (ch == '='){ // end of keyword | ||||
| String keyword = coll.toString("UTF-8"); | String keyword = coll.toString("UTF-8"); | ||||
| // Get rest of entry | // Get rest of entry | ||||
| byte[] rest = new byte[len - read]; | |||||
| int got = i.read(rest); | |||||
| if (got != len - read){ | |||||
| final int restLen = len - read; | |||||
| byte[] rest = new byte[restLen]; | |||||
| int got = 0; | |||||
| while (got < restLen && (ch = i.read()) != -1) { | |||||
| rest[got++] = (byte) ch; | |||||
| } | |||||
| if (got != restLen) { | |||||
| throw new IOException("Failed to read " | throw new IOException("Failed to read " | ||||
| + "Paxheader. Expected " | + "Paxheader. Expected " | ||||
| + (len - read) | |||||
| + restLen | |||||
| + " bytes, read " | + " bytes, read " | ||||
| + got); | + got); | ||||
| } | } | ||||
| // Drop trailing NL | // Drop trailing NL | ||||
| String value = new String(rest, 0, | String value = new String(rest, 0, | ||||
| len - read - 1, "UTF-8"); | |||||
| restLen - 1, "UTF-8"); | |||||
| headers.put(keyword, value); | headers.put(keyword, value); | ||||
| break; | break; | ||||
| } | } | ||||
| @@ -60,7 +60,7 @@ public class TarUtils { | |||||
| public String decode(byte[] buffer) { | public String decode(byte[] buffer) { | ||||
| final int length = buffer.length; | final int length = buffer.length; | ||||
| StringBuffer result = new StringBuffer(length); | |||||
| StringBuilder result = new StringBuilder(length); | |||||
| for (int i = 0; i < length; ++i) { | for (int i = 0; i < length; ++i) { | ||||
| byte b = buffer[i]; | byte b = buffer[i]; | ||||
| @@ -130,10 +130,6 @@ public class TarUtils { | |||||
| end--; | end--; | ||||
| trailer = buffer[end - 1]; | trailer = buffer[end - 1]; | ||||
| } | } | ||||
| if (start == end) { | |||||
| throw new IllegalArgumentException( | |||||
| exceptionMessage(buffer, offset, length, start, trailer)); | |||||
| } | |||||
| for ( ;start < end; start++) { | for ( ;start < end; start++) { | ||||
| final byte currentByte = buffer[start]; | final byte currentByte = buffer[start]; | ||||
| @@ -194,7 +190,7 @@ public class TarUtils { | |||||
| if (negative) { | if (negative) { | ||||
| // 2's complement | // 2's complement | ||||
| val--; | val--; | ||||
| val ^= ((long) Math.pow(2, (length - 1) * 8) - 1); | |||||
| val ^= (long) Math.pow(2, (length - 1) * 8) - 1; | |||||
| } | } | ||||
| return negative ? -val : val; | return negative ? -val : val; | ||||
| } | } | ||||
| @@ -236,7 +232,15 @@ public class TarUtils { | |||||
| // Helper method to generate the exception message | // Helper method to generate the exception message | ||||
| private static String exceptionMessage(byte[] buffer, final int offset, | private static String exceptionMessage(byte[] buffer, final int offset, | ||||
| final int length, int current, final byte currentByte) { | final int length, int current, final byte currentByte) { | ||||
| String string = new String(buffer, offset, length); // TODO default charset? | |||||
| // default charset is good enough for an exception message, | |||||
| // | |||||
| // the alternative was to modify parseOctal and | |||||
| // parseOctalOrBinary to receive the ZipEncoding of the | |||||
| // archive (deprecating the existing public methods, of | |||||
| // course) and dealing with the fact that ZipEncoding#decode | |||||
| // can throw an IOException which parseOctal* doesn't declare | |||||
| String string = new String(buffer, offset, length); | |||||
| string=string.replaceAll("\0", "{NUL}"); // Replace NULs to allow string to be printed | string=string.replaceAll("\0", "{NUL}"); // Replace NULs to allow string to be printed | ||||
| final String s = "Invalid byte "+currentByte+" at offset "+(current-offset)+" in '"+string+"' len="+length; | final String s = "Invalid byte "+currentByte+" at offset "+(current-offset)+" in '"+string+"' len="+length; | ||||
| return s; | return s; | ||||
| @@ -549,8 +553,8 @@ public class TarUtils { | |||||
| public static long computeCheckSum(final byte[] buf) { | public static long computeCheckSum(final byte[] buf) { | ||||
| long sum = 0; | long sum = 0; | ||||
| for (int i = 0; i < buf.length; ++i) { | |||||
| sum += BYTE_MASK & buf[i]; | |||||
| for (byte element : buf) { | |||||
| sum += BYTE_MASK & element; | |||||
| } | } | ||||
| return sum; | return sum; | ||||
| @@ -31,7 +31,6 @@ import static org.junit.Assert.assertEquals; | |||||
| public class ReplaceTokensTest { | public class ReplaceTokensTest { | ||||
| @Rule | @Rule | ||||
| public BuildFileRule buildRule = new BuildFileRule(); | public BuildFileRule buildRule = new BuildFileRule(); | ||||
| @@ -45,7 +44,7 @@ public class ReplaceTokensTest { | |||||
| buildRule.executeTarget("testReplaceTokens"); | buildRule.executeTarget("testReplaceTokens"); | ||||
| File expected = buildRule.getProject().resolveFile("expected/replacetokens.test"); | File expected = buildRule.getProject().resolveFile("expected/replacetokens.test"); | ||||
| File result = new File(buildRule.getProject().getProperty("output"), "replacetokens.test"); | File result = new File(buildRule.getProject().getProperty("output"), "replacetokens.test"); | ||||
| assertEquals(FileUtilities.getFileContents(expected), FileUtilities.getFileContents(result)); | |||||
| assertEquals(FileUtilities.getFileContents(expected), FileUtilities.getFileContents(result)); | |||||
| } | } | ||||
| @Test | @Test | ||||
| @@ -56,4 +55,27 @@ public class ReplaceTokensTest { | |||||
| assertEquals(FileUtilities.getFileContents(expected), FileUtilities.getFileContents(result)); | assertEquals(FileUtilities.getFileContents(expected), FileUtilities.getFileContents(result)); | ||||
| } | } | ||||
| @Test | |||||
| public void testReplaceTokensDoubleEncoded() throws IOException { | |||||
| buildRule.executeTarget("testReplaceTokensDoubleEncoded"); | |||||
| File expected = buildRule.getProject().resolveFile("expected/replacetokens.double.test"); | |||||
| File result = new File(buildRule.getProject().getProperty("output"), "replacetokens.double.test"); | |||||
| assertEquals(FileUtilities.getFileContents(expected), FileUtilities.getFileContents(result)); | |||||
| } | |||||
| @Test | |||||
| public void testReplaceTokensDoubleEncodedToSimple() throws IOException { | |||||
| buildRule.executeTarget("testReplaceTokensDoubleEncodedToSimple"); | |||||
| File expected = buildRule.getProject().resolveFile("expected/replacetokens.test"); | |||||
| File result = new File(buildRule.getProject().getProperty("output"), "replacetokens.double.test"); | |||||
| assertEquals(FileUtilities.getFileContents(expected), FileUtilities.getFileContents(result)); | |||||
| } | |||||
| @Test | |||||
| public void testReplaceTokensMustacheStyle() throws IOException { | |||||
| buildRule.executeTarget("testReplaceTokensMustacheStyle"); | |||||
| File expected = buildRule.getProject().resolveFile("expected/replacetokens.test"); | |||||
| File result = new File(buildRule.getProject().getProperty("output"), "replacetokens.mustache.test"); | |||||
| assertEquals(FileUtilities.getFileContents(expected), FileUtilities.getFileContents(result)); | |||||
| } | |||||
| } | } | ||||