diff --git a/src/main/java/com/threerings/getdown/util/ConfigUtil.java b/src/main/java/com/threerings/getdown/util/ConfigUtil.java index 293da85..c9705e8 100644 --- a/src/main/java/com/threerings/getdown/util/ConfigUtil.java +++ b/src/main/java/com/threerings/getdown/util/ConfigUtil.java @@ -23,17 +23,16 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package com.threerings.getdown.util; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import com.samskivert.io.StreamUtil; import com.samskivert.util.StringUtil; import static com.threerings.getdown.Log.log; @@ -48,81 +47,30 @@ public class ConfigUtil * Parses a configuration file containing key/value pairs. The file must be in the UTF-8 * encoding. * + * @param checkPlatform if true, platform qualifiers will be used to filter out pairs that do + * not match the current platform; if false, all pairs will be returned. + * * @return a list of String[] instances containing the key/value pairs in the * order they were parsed from the file. */ public static List parsePairs (File config, boolean checkPlatform) throws IOException { - ArrayList pairs = new ArrayList(); - String osname = System.getProperty("os.name"); - osname = (osname == null) ? "" : osname.toLowerCase(); + // annoyingly FileReader does not allow encoding to be specified (uses platform default) + return parsePairs( + new InputStreamReader(new FileInputStream(config), "UTF-8"), checkPlatform); + } - // parse our configuration file - FileInputStream fin = null; - try { - fin = new FileInputStream(config); - BufferedReader bin = new BufferedReader(new InputStreamReader(fin, "UTF-8")); - String line = null; - while ((line = bin.readLine()) != null) { - // nix comments - int cidx = line.indexOf("#"); - if (cidx != -1) { - line = line.substring(0, cidx); - } - - // trim whitespace and skip blank lines - line = line.trim(); - if (StringUtil.isBlank(line)) { - continue; - } - - // parse our key/value pair - String[] pair = new String[2]; - int eidx = line.indexOf("="); - if (eidx != -1) { - pair[0] = line.substring(0, eidx).trim(); - pair[1] = line.substring(eidx+1).trim(); - } else { - pair[0] = line; - pair[1] = ""; - } - - // allow a value to have a [Linux] - if (pair[1].startsWith("[")) { - cidx = pair[1].indexOf("]"); - if (cidx == -1) { - log.warning("Bogus platform specifier [key=" + pair[0] + - ", value=" + pair[1] + "]."); - } else { - String platform = pair[1].substring(1, cidx); - platform = platform.trim().toLowerCase(); - pair[1] = pair[1].substring(cidx+1).trim(); - if (checkPlatform) { - if (platform.startsWith("!")) { - platform = platform.substring(1); - if (osname.indexOf(platform) != -1) { - log.info("Skipping [platform=!" + platform + - ", key=" + pair[0] + ", value=" + pair[1] + "]."); - continue; - } - } else if (osname.indexOf(platform) == -1) { - log.info("Skipping [platform=" + platform + - ", key=" + pair[0] + ", value=" + pair[1] + "]."); - continue; - } - } - } - } - - pairs.add(pair); - } - - } finally { - StreamUtil.close(fin); - } - - return pairs; + /** + * See {@link #parsePairs(File,boolean}. + */ + public static List parsePairs (Reader config, boolean checkPlatform) + throws IOException + { + return parsePairs( + config, + checkPlatform ? StringUtil.deNull(System.getProperty("os.name")).toLowerCase() : null, + checkPlatform ? StringUtil.deNull(System.getProperty("os.arch")).toLowerCase() : null); } /** @@ -171,4 +119,90 @@ public class ConfigUtil return (String[])value; } } + + /** + * A helper function for {@link #parsePairs(Reader,boolean}. + */ + protected static List parsePairs (Reader config, String osname, String osarch) + throws IOException + { + List pairs = new ArrayList(); + for (String line : FileUtil.readLines(config)) { + // nix comments + int cidx = line.indexOf("#"); + if (cidx != -1) { + line = line.substring(0, cidx); + } + + // trim whitespace and skip blank lines + line = line.trim(); + if (StringUtil.isBlank(line)) { + continue; + } + + // parse our key/value pair + String[] pair = new String[2]; + int eidx = line.indexOf("="); + if (eidx != -1) { + pair[0] = line.substring(0, eidx).trim(); + pair[1] = line.substring(eidx+1).trim(); + } else { + pair[0] = line; + pair[1] = ""; + } + + // if the pair has an os qualifier, we need to process it + if (pair[1].startsWith("[")) { + int qidx = pair[1].indexOf("]"); + if (qidx == -1) { + log.warning("Bogus platform specifier", "key", pair[0], "value", pair[1]); + continue; // omit the pair entirely + } + // if we're checking qualifiers and the os doesn't match this qualifier, skip it + String quals = pair[1].substring(1, qidx); + if (osname != null && !checkQualifiers(quals, osname, osarch)) { + log.info("Skipping", "quals", quals, "osname", osname, "osarch", osarch, + "key", pair[0], "value", pair[1]); + continue; + } + // otherwise filter out the qualifier text + pair[1] = pair[1].substring(qidx+1).trim(); + } + + pairs.add(pair); + } + + return pairs; + } + + /** + * A helper function for {@link #parsePairs(Reader,String,String)}. Qualifiers have the + * following form: + *
+     * id = os[-arch]
+     * ids = id | id,ids
+     * quals = !id | ids
+     * 
+ * Examples: [linux-amd64,linux-x86_64], [windows], [mac os x], [!windows]. + */ + protected static boolean checkQualifiers (String quals, String osname, String osarch) + { + for (String qual : quals.split(",")) { + String[] bits = qual.trim().toLowerCase().split("-"); + String os = bits[0], arch = (bits.length > 1) ? bits[1] : ""; + if (os.startsWith("!")) { + // if we have a negative match, we can immediately return false + if (osname.indexOf(os.substring(1)) != -1 && osarch.indexOf(arch) != -1) { + return false; + } + } else { + // if we have a positive match, we can immediately return true + if (osname.indexOf(os) != -1 && osarch.indexOf(arch) != -1) { + return true; + } + } + } + // we had no positive matches, so return false + return false; + } } diff --git a/src/main/java/com/threerings/getdown/util/FileUtil.java b/src/main/java/com/threerings/getdown/util/FileUtil.java index b9bfa96..c5f7131 100644 --- a/src/main/java/com/threerings/getdown/util/FileUtil.java +++ b/src/main/java/com/threerings/getdown/util/FileUtil.java @@ -23,10 +23,15 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package com.threerings.getdown.util; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.Reader; + +import java.util.ArrayList; +import java.util.List; import com.samskivert.io.StreamUtil; @@ -92,4 +97,21 @@ public class FileUtil StreamUtil.close(fout); } } + + /** + * Reads the contents of the supplied input stream into a list of lines. Closes the reader on + * successful or failed completion. + */ + public static List readLines (Reader in) + throws IOException + { + List lines = new ArrayList(); + try { + BufferedReader bin = new BufferedReader(in); + for (String line = null; (line = bin.readLine()) != null; lines.add(line)) {} + } finally { + StreamUtil.close(in); + } + return lines; + } } diff --git a/src/test/java/com/threerings/getdown/util/ConfigUtilTest.java b/src/test/java/com/threerings/getdown/util/ConfigUtilTest.java new file mode 100644 index 0000000..e88496f --- /dev/null +++ b/src/test/java/com/threerings/getdown/util/ConfigUtilTest.java @@ -0,0 +1,170 @@ +// +// $Id$ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2010 Three Rings Design, Inc. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.threerings.getdown.util; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +import com.samskivert.util.RandomUtil; +import com.samskivert.util.StringUtil; + +import org.junit.*; +import static org.junit.Assert.*; + +/** + * Tests {@link ConfigUtil}. + */ +public class ConfigUtilTest +{ + public static class Pair { + public final String key; + public final String value; + public Pair (String key, String value) { + this.key = key; + this.value = value; + } + } + + public static final Pair[] SIMPLE_PAIRS = { + new Pair("one", "two"), + new Pair("three", "four"), + new Pair("five", "six"), + new Pair("seven", "eight"), + new Pair("nine", "ten"), + }; + + @Test public void testSimplePairs () throws IOException + { + List pairs = ConfigUtil.parsePairs(toReader(SIMPLE_PAIRS), true); + for (int ii = 0; ii < SIMPLE_PAIRS.length; ii++) { + assertEquals(SIMPLE_PAIRS[ii].key, pairs.get(ii)[0]); + assertEquals(SIMPLE_PAIRS[ii].value, pairs.get(ii)[1]); + } + } + + @Test public void testQualifiedPairs () throws IOException + { + Pair linux = new Pair("one", "[linux] two"); + Pair mac = new Pair("three", "[mac os x] four"); + Pair linuxAndMac = new Pair("five", "[linux, mac os x] six"); + Pair linux64 = new Pair("seven", "[linux-x86_64] eight"); + Pair linux64s = new Pair("nine", "[linux-x86_64, linux-amd64] ten"); + Pair mac64 = new Pair("eleven", "[mac os x-x86_64] twelve"); + Pair win64 = new Pair("thirteen", "[windows-x86_64] fourteen"); + Pair[] pairs = { linux, mac, linuxAndMac, linux64, linux64s, mac64, win64 }; + + List parsed = ConfigUtil.parsePairs(toReader(pairs), "linux", "i386"); + assertTrue(exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + + parsed = ConfigUtil.parsePairs(toReader(pairs), "linux", "x86_64"); + assertTrue(exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(exists(parsed, linux64.key)); + assertTrue(exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + + parsed = ConfigUtil.parsePairs(toReader(pairs), "linux", "amd64"); + assertTrue(exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + + parsed = ConfigUtil.parsePairs(toReader(pairs), "mac os x", "x86_64"); + assertTrue(!exists(parsed, linux.key)); + assertTrue(exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + + parsed = ConfigUtil.parsePairs(toReader(pairs), "windows", "i386"); + assertTrue(!exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(!exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + + parsed = ConfigUtil.parsePairs(toReader(pairs), "windows", "x86_64"); + assertTrue(!exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(!exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(exists(parsed, win64.key)); + + parsed = ConfigUtil.parsePairs(toReader(pairs), "windows", "amd64"); + assertTrue(!exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(!exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + } + + protected static boolean exists (List pairs, String key) + { + for (String[] pair : pairs) { + if (pair[0].equals(key)) { + return true; + } + } + return false; + } + + protected static StringReader toReader (Pair[] pairs) + { + StringBuilder builder = new StringBuilder(); + for (Pair pair : pairs) { + // throw some whitespace in to ensure it's trimmed + builder.append(whitespace()).append(pair.key). + append(whitespace()).append("="). + append(whitespace()).append(pair.value). + append(whitespace()).append("\n"); + } + return new StringReader(builder.toString()); + } + + protected static String whitespace () + { + return RandomUtil.getBoolean() ? " " : ""; + } +} diff --git a/src/test/java/com/threerings/getdown/util/FileUtilTest.java b/src/test/java/com/threerings/getdown/util/FileUtilTest.java new file mode 100644 index 0000000..9c1f40c --- /dev/null +++ b/src/test/java/com/threerings/getdown/util/FileUtilTest.java @@ -0,0 +1,49 @@ +// +// $Id$ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2010 Three Rings Design, Inc. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.threerings.getdown.util; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +import org.junit.*; +import static org.junit.Assert.*; + +/** + * Tests {@link FileUtil}. + */ +public class FileUtilTest +{ + @Test public void testReadLines () throws IOException + { + String data = "This is a test\nof a file with\na few lines\n"; + List lines = FileUtil.readLines(new StringReader(data)); + String[] linesBySplit = data.split("\n"); + assertEquals(linesBySplit.length, lines.size()); + for (int ii = 0; ii < lines.size(); ii++) { + assertEquals(linesBySplit[ii], lines.get(ii)); + } + } +}