diff --git a/src/main/java/com/threerings/getdown/data/Application.java b/src/main/java/com/threerings/getdown/data/Application.java index d22cc92..b36b12e 100644 --- a/src/main/java/com/threerings/getdown/data/Application.java +++ b/src/main/java/com/threerings/getdown/data/Application.java @@ -510,19 +510,19 @@ public class Application public UpdateInterface init (boolean checkPlatform) throws IOException { - Map cdata = null; - File config = _config; - ConfigUtil.ParseOpts opts = ConfigUtil.createOpts(checkPlatform); + Config config = null; + File cfgfile = _config; + Config.ParseOpts opts = Config.createOpts(checkPlatform); try { // if we have a configuration file, read the data from it - if (config.exists()) { - cdata = ConfigUtil.parseConfig(_config, opts); + if (cfgfile.exists()) { + config = Config.parseConfig(_config, opts); } // otherwise, try reading data from our backup config file; thanks to funny windows // bullshit, we have to do this backup file fiddling in case we got screwed while // updating getdown.txt during normal operation - else if ((config = getLocalPath(CONFIG_FILE + "_old")).exists()) { - cdata = ConfigUtil.parseConfig(config, opts); + else if ((cfgfile = getLocalPath(CONFIG_FILE + "_old")).exists()) { + config = Config.parseConfig(cfgfile, opts); } // otherwise, issue a warning that we found no getdown file else { @@ -534,16 +534,17 @@ public class Application // if we failed to read our config file, check for an appbase specified via a system // property; we can use that to bootstrap ourselves back into operation - if (cdata == null) { + if (config == null) { String appbase = SysProps.appBase(); log.info("Attempting to obtain 'appbase' from system property", "appbase", appbase); - cdata = new HashMap<>(); + Map cdata = new HashMap<>(); cdata.put("appbase", appbase); + config = new Config(cdata); } // first determine our application base, this way if anything goes wrong later in the // process, our caller can use the appbase to download a new configuration file - _appbase = (String)cdata.get("appbase"); + _appbase = config.getString("appbase"); if (_appbase == null) { throw new RuntimeException("m.missing_appbase"); } @@ -557,8 +558,7 @@ public class Application } // extract our version information - String vstr = (String)cdata.get("version"); - if (vstr != null) _version = parseLong(vstr, "m.invalid_version"); + _version = config.getLong("version", -1L); // if we are a versioned deployment, create a versioned appbase try { @@ -569,7 +569,7 @@ public class Application } // check for a latest config URL - String latest = (String)cdata.get("latest"); + String latest = config.getString("latest"); if (latest != null) { if (latest.startsWith(_appbase)) { latest = _appbase + latest.substring(_appbase.length()); @@ -586,49 +586,41 @@ public class Application String appPrefix = StringUtil.isBlank(_appid) ? "" : (_appid + "."); // determine our application class name - _class = (String)cdata.get(appPrefix + "class"); + _class = config.getString(appPrefix + "class"); if (_class == null) { throw new IOException("m.missing_class"); } // determine whether we want strict comments - _strictComments = Boolean.parseBoolean((String)cdata.get("strict_comments")); + _strictComments = config.getBoolean("strict_comments"); // check to see if we're using a custom java.version property and regex - vstr = (String)cdata.get("java_version_prop"); - if (vstr != null) _javaVersionProp = vstr; - vstr = (String)cdata.get("java_version_regex"); - if (vstr != null) _javaVersionRegex = vstr; + _javaVersionProp = config.getString("java_version_prop", _javaVersionProp); + _javaVersionRegex = config.getString("java_version_regex", _javaVersionRegex); // check to see if we require a particular JVM version and have a supplied JVM - vstr = (String)cdata.get("java_version"); - if (vstr != null) _javaMinVersion = parseLong(vstr, "m.invalid_java_version"); + _javaMinVersion = config.getLong("java_version", _javaMinVersion); // we support java_min_version as an alias of java_version; it better expresses the check // that's going on and better mirrors java_max_version - vstr = (String)cdata.get("java_min_version"); - if (vstr != null) _javaMinVersion = parseLong(vstr, "m.invalid_java_version"); - + _javaMinVersion = config.getLong("java_min_version", _javaMinVersion); // check to see if we require a particular max JVM version and have a supplied JVM - vstr = (String)cdata.get("java_max_version"); - if (vstr != null) _javaMaxVersion = parseLong(vstr, "m.invalid_java_version"); - + _javaMaxVersion = config.getLong("java_max_version", _javaMaxVersion); // check to see if we require a particular JVM version and have a supplied JVM - vstr = (String)cdata.get("java_exact_version_required"); - _javaExactVersionRequired = Boolean.parseBoolean(vstr); + _javaExactVersionRequired = config.getBoolean("java_exact_version_required"); // this is a little weird, but when we're run from the digester, we see a String[] which // contains java locations for all platforms which we can't grok, but the digester doesn't // need to know about that; when we're run in a real application there will be only one! - Object javaloc = cdata.get("java_location"); + Object javaloc = config.getRaw("java_location"); if (javaloc instanceof String) { _javaLocation = (String)javaloc; } // determine whether we have any tracking configuration - _trackingURL = (String)cdata.get("tracking_url"); + _trackingURL = config.getString("tracking_url"); // check for tracking progress percent configuration - String trackPcts = (String)cdata.get("tracking_percents"); + String trackPcts = config.getString("tracking_percents"); if (!StringUtil.isBlank(trackPcts)) { _trackingPcts = new HashSet<>(); for (int pct : StringUtil.parseIntArray(trackPcts)) { @@ -640,14 +632,14 @@ public class Application } // Check for tracking cookie configuration - _trackingCookieName = (String)cdata.get("tracking_cookie_name"); - _trackingCookieProperty = (String)cdata.get("tracking_cookie_property"); + _trackingCookieName = config.getString("tracking_cookie_name"); + _trackingCookieProperty = config.getString("tracking_cookie_property"); // Some app may need an extra suffix added to the tracking URL - _trackingURLSuffix = (String)cdata.get("tracking_url_suffix"); + _trackingURLSuffix = config.getString("tracking_url_suffix"); // Some app may need to generate google analytics code - _trackingGAHash = (String)cdata.get("tracking_ga_hash"); + _trackingGAHash = config.getString("tracking_ga_hash"); // clear our arrays as we may be reinitializing _codes.clear(); @@ -658,34 +650,34 @@ public class Application _txtJvmArgs.clear(); // parse our code resources - if (ConfigUtil.getMultiValue(cdata, "code") == null && - ConfigUtil.getMultiValue(cdata, "ucode") == null) { + if (config.getMultiValue("code") == null && + config.getMultiValue("ucode") == null) { throw new IOException("m.missing_code"); } - parseResources(cdata, "code", Resource.NORMAL, _codes); - parseResources(cdata, "ucode", Resource.UNPACK, _codes); + parseResources(config, "code", Resource.NORMAL, _codes); + parseResources(config, "ucode", Resource.UNPACK, _codes); // parse our non-code resources - parseResources(cdata, "resource", Resource.NORMAL, _resources); - parseResources(cdata, "uresource", Resource.UNPACK, _resources); - parseResources(cdata, "xresource", Resource.EXEC, _resources); + parseResources(config, "resource", Resource.NORMAL, _resources); + parseResources(config, "uresource", Resource.UNPACK, _resources); + parseResources(config, "xresource", Resource.EXEC, _resources); // parse our auxiliary resource groups - for (String auxgroup : parseList(cdata, "auxgroups")) { + for (String auxgroup : config.getList("auxgroups")) { ArrayList codes = new ArrayList<>(); - parseResources(cdata, auxgroup + ".code", Resource.NORMAL, codes); - parseResources(cdata, auxgroup + ".ucode", Resource.UNPACK, codes); + parseResources(config, auxgroup + ".code", Resource.NORMAL, codes); + parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes); ArrayList rsrcs = new ArrayList<>(); - parseResources(cdata, auxgroup + ".resource", Resource.NORMAL, rsrcs); - parseResources(cdata, auxgroup + ".uresource", Resource.UNPACK, rsrcs); + parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs); + parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs); _auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs)); } // transfer our JVM arguments (we include both "global" args and app_id-prefixed args) - String[] jvmargs = ConfigUtil.getMultiValue(cdata, "jvmarg"); + String[] jvmargs = config.getMultiValue("jvmarg"); addAll(jvmargs, _jvmargs); if (appPrefix.length() > 0) { - jvmargs = ConfigUtil.getMultiValue(cdata, appPrefix + "jvmarg"); + jvmargs = config.getMultiValue(appPrefix + "jvmarg"); addAll(jvmargs, _jvmargs); } @@ -693,10 +685,10 @@ public class Application addAll(_extraJvmArgs, _jvmargs); // get the set of optimum JVM arguments - _optimumJvmArgs = ConfigUtil.getMultiValue(cdata, "optimum_jvmarg"); + _optimumJvmArgs = config.getMultiValue("optimum_jvmarg"); // transfer our application arguments - String[] appargs = ConfigUtil.getMultiValue(cdata, appPrefix + "apparg"); + String[] appargs = config.getMultiValue(appPrefix + "apparg"); addAll(appargs, _appargs); // add the launch specific application arguments @@ -706,33 +698,31 @@ public class Application fillAssignmentListFromPairs("extra.txt", _txtJvmArgs); // determine whether we want to allow offline operation (defaults to false) - _allowOffline = Boolean.parseBoolean((String)cdata.get("allow_offline")); + _allowOffline = config.getBoolean("allow_offline"); // look for a debug.txt file which causes us to run in java.exe on Windows so that we can // obtain a thread dump of the running JVM _windebug = getLocalPath("debug.txt").exists(); // whether to cache code resources and launch from cache - _useCodeCache = Boolean.parseBoolean((String) cdata.get("use_code_cache")); - String ccRetentionDays = (String) cdata.get("code_cache_retention_days"); - _codeCacheRetentionDays = ccRetentionDays == null ? 7 : - Integer.parseInt(ccRetentionDays); + _useCodeCache = config.getBoolean("use_code_cache"); + _codeCacheRetentionDays = config.getInt("code_cache_retention_days", 7); // parse and return our application config UpdateInterface ui = new UpdateInterface(); - _name = ui.name = (String)cdata.get("ui.name"); - ui.progress = parseRect(cdata, "ui.progress", ui.progress); - ui.progressText = parseColor(cdata, "ui.progress_text", ui.progressText); - ui.hideProgressText = Boolean.parseBoolean((String)cdata.get("ui.hide_progress_text")); - ui.minShowSeconds = parseInt(cdata, "ui.min_show_seconds", ui.minShowSeconds); - ui.progressBar = parseColor(cdata, "ui.progress_bar", ui.progressBar); - ui.status = parseRect(cdata, "ui.status", ui.status); - ui.statusText = parseColor(cdata, "ui.status_text", ui.statusText); - ui.textShadow = parseColor(cdata, "ui.text_shadow", ui.textShadow); - ui.hideDecorations = Boolean.parseBoolean((String)cdata.get("ui.hide_decorations")); - ui.backgroundImage = (String)cdata.get("ui.background_image"); + _name = ui.name = config.getString("ui.name"); + ui.progress = config.getRect("ui.progress", ui.progress); + ui.progressText = config.getColor("ui.progress_text", ui.progressText); + ui.hideProgressText = config.getBoolean("ui.hide_progress_text"); + ui.minShowSeconds = config.getInt("ui.min_show_seconds", ui.minShowSeconds); + ui.progressBar = config.getColor("ui.progress_bar", ui.progressBar); + ui.status = config.getRect("ui.status", ui.status); + ui.statusText = config.getColor("ui.status_text", ui.statusText); + ui.textShadow = config.getColor("ui.text_shadow", ui.textShadow); + ui.hideDecorations = config.getBoolean("ui.hide_decorations"); + ui.backgroundImage = config.getString("ui.background_image"); if (ui.backgroundImage == null) { // support legacy format - ui.backgroundImage = (String)cdata.get("ui.background"); + ui.backgroundImage = config.getString("ui.background"); } // and now ui.background can refer to the background color, but fall back to black // or white, depending on the brightness of the progressText @@ -741,32 +731,32 @@ public class Application null)[2]) ? Color.BLACK : Color.WHITE; - ui.background = parseColor(cdata, "ui.background", defaultBackground); - ui.progressImage = (String)cdata.get("ui.progress_image"); - ui.rotatingBackgrounds = ConfigUtil.getMultiValue(cdata, "ui.rotating_background"); - ui.iconImages = ConfigUtil.getMultiValue(cdata, "ui.icon"); - ui.errorBackground = (String)cdata.get("ui.error_background"); - _dockIconPath = (String)cdata.get("ui.mac_dock_icon"); + ui.background = config.getColor("ui.background", defaultBackground); + ui.progressImage = config.getString("ui.progress_image"); + ui.rotatingBackgrounds = config.getMultiValue("ui.rotating_background"); + ui.iconImages = config.getMultiValue("ui.icon"); + ui.errorBackground = config.getString("ui.error_background"); + _dockIconPath = config.getString("ui.mac_dock_icon"); if (_dockIconPath == null) { _dockIconPath = "../desktop.icns"; // use a sensible default } // On an installation error, where do we point the user. - String installError = parseUrl(cdata, "ui.install_error", null); + String installError = config.getUrl("ui.install_error", null); ui.installError = (installError == null) ? "m.default_install_error" : MessageUtil.taint(installError); // the patch notes bits - ui.patchNotes = parseRect(cdata, "ui.patch_notes", ui.patchNotes); - ui.patchNotesUrl = parseUrl(cdata, "ui.patch_notes_url", null); + ui.patchNotes = config.getRect("ui.patch_notes", ui.patchNotes); + ui.patchNotesUrl = config.getUrl("ui.patch_notes_url", null); // the play again bits - ui.playAgain = parseRect(cdata, "ui.play_again", ui.playAgain); - ui.playAgainImage = (String)cdata.get("ui.play_again_image"); + ui.playAgain = config.getRect("ui.play_again", ui.playAgain); + ui.playAgainImage = config.getString("ui.play_again_image"); // step progress percentages for (UpdateInterface.Step step : UpdateInterface.Step.values()) { - String spec = (String)cdata.get("ui.percents." + step.name()); + String spec = config.getString("ui.percents." + step.name()); if (spec != null) { try { ui.stepPercentages.put(step, intsToList(StringUtil.parseIntArray(spec))); @@ -787,7 +777,7 @@ public class Application File pairFile = getLocalPath(pairLocation); if (pairFile.exists()) { try { - List args = ConfigUtil.parsePairs(pairFile, ConfigUtil.createOpts(false)); + List args = Config.parsePairs(pairFile, Config.createOpts(false)); for (String[] pair : args) { if (pair[1].length() == 0) { collector.add(pair[0]); @@ -1253,7 +1243,7 @@ public class Application try { in = ConnectionUtil.open(_latest, 0, 0).getInputStream(); BufferedReader bin = new BufferedReader(new InputStreamReader(in)); - for (String[] pair : ConfigUtil.parsePairs(bin, ConfigUtil.createOpts(false))) { + for (String[] pair : Config.parsePairs(bin, Config.createOpts(false))) { if (pair[0].equals("version")) { _targetVersion = Math.max(Long.parseLong(pair[1]), _targetVersion); if (fileVersion != -1 && _targetVersion > fileVersion) { @@ -1707,10 +1697,10 @@ public class Application } /** Used to parse resources with the specified name. */ - protected void parseResources (Map cdata, String name, - EnumSet attrs, List list) + protected void parseResources (Config config, String name, EnumSet attrs, + List list) { - String[] rsrcs = ConfigUtil.getMultiValue(cdata, name); + String[] rsrcs = config.getMultiValue(name); if (rsrcs == null) { return; } @@ -1723,87 +1713,6 @@ public class Application } } - /** Used to parse rectangle specifications from the config file. */ - protected Rectangle parseRect (Map cdata, String name, Rectangle def) - { - String value = (String)cdata.get(name); - Rectangle rect = parseRect(name, value); - return (rect == null) ? def : rect; - } - - /** - * Takes a comma-separated String of four integers and returns a rectangle using those ints as - * the its x, y, width, and height. - */ - public static Rectangle parseRect (String name, String value) - { - if (!StringUtil.isBlank(value)) { - int[] v = StringUtil.parseIntArray(value); - if (v != null && v.length == 4) { - return new Rectangle(v[0], v[1], v[2], v[3]); - } - log.warning("Ignoring invalid rect '" + name + "' config '" + value + "'."); - } - return null; - } - - /** Used to parse int specifications from the config file. */ - protected int parseInt (Map cdata, String name, int def) { - String value = (String)cdata.get(name); - try { - return value == null ? def : Integer.parseInt(value); - } catch (Exception e) { - log.warning("Ignoring invalid int '" + name + "' config '" + value + "',"); - return def; - } - } - - /** Used to parse color specifications from the config file. */ - protected Color parseColor (Map cdata, String name, Color def) - { - String value = (String)cdata.get(name); - Color color = parseColor(value); - return (color == null) ? def : color; - } - - /** - * Parses the given hex color value (e.g. FFFFF) and returns a Color object with that value. If - * the given value is null of not a valid hexadecimal number, this will return null. - */ - public static Color parseColor (String hexValue) - { - if (!StringUtil.isBlank(hexValue)) { - try { - int rgba = Integer.parseInt(hexValue, 16); - boolean hasAlpha = hexValue.length() > 6; - return new Color(rgba, hasAlpha); - } catch (NumberFormatException e) { - log.warning("Ignoring invalid color", "hexValue", hexValue, "exception", e); - } - } - return null; - } - - /** Parses a list of strings from the config file. */ - protected String[] parseList (Map cdata, String name) - { - String value = (String)cdata.get(name); - return (value == null) ? ArrayUtil.EMPTY_STRING : StringUtil.parseStringArray(value); - } - - /** - * Parses a URL from the config file, checking first for a localized version. - */ - protected String parseUrl (Map cdata, String name, String def) - { - String value = (String)cdata.get(name + "." + Locale.getDefault().getLanguage()); - if (!StringUtil.isBlank(value)) { - return value; - } - value = (String)cdata.get(name); - return StringUtil.isBlank(value) ? def : value; - } - /** Possibly generates and returns a google analytics tracking cookie. */ protected String getGATrackingCode () { @@ -1843,20 +1752,6 @@ public class Application } } - /** - * Parses and returns a long. {@code value} must be non-null. - * @throws IOException on malformed value. - */ - protected static long parseLong (String value, String errkey) throws IOException - { - try { - return Long.parseLong(value); - } catch (Exception e) { - String err = MessageUtil.tcompose(errkey, value); - throw (IOException) new IOException(err).initCause(e); - } - } - protected File _appdir; protected String _appid; protected File _config; diff --git a/src/main/java/com/threerings/getdown/data/Digest.java b/src/main/java/com/threerings/getdown/data/Digest.java index 6239140..32eb1fa 100644 --- a/src/main/java/com/threerings/getdown/data/Digest.java +++ b/src/main/java/com/threerings/getdown/data/Digest.java @@ -15,7 +15,7 @@ import com.samskivert.io.StreamUtil; import com.samskivert.text.MessageUtil; import com.samskivert.util.StringUtil; -import com.threerings.getdown.util.ConfigUtil; +import com.threerings.getdown.util.Config; import com.threerings.getdown.util.ProgressObserver; import static com.threerings.getdown.Log.log; @@ -155,12 +155,12 @@ public class Digest String filename = digestFile(version); StringBuilder data = new StringBuilder(); File dfile = new File(appdir, filename); - ConfigUtil.ParseOpts opts = ConfigUtil.createOpts(false); + Config.ParseOpts opts = Config.createOpts(false); opts.strictComments = strictComments; // bias = toward key: the key is the filename and could conceivably contain = signs, value // is the hex encoded hash which will not contain = opts.biasToKey = true; - for (String[] pair : ConfigUtil.parsePairs(dfile, opts)) { + for (String[] pair : Config.parsePairs(dfile, opts)) { if (pair[0].equals(filename)) { _metaDigest = pair[1]; break; diff --git a/src/main/java/com/threerings/getdown/launcher/Getdown.java b/src/main/java/com/threerings/getdown/launcher/Getdown.java index e7be647..8084861 100644 --- a/src/main/java/com/threerings/getdown/launcher/Getdown.java +++ b/src/main/java/com/threerings/getdown/launcher/Getdown.java @@ -56,7 +56,7 @@ import com.threerings.getdown.data.SysProps; import com.threerings.getdown.net.Downloader; import com.threerings.getdown.net.HTTPDownloader; import com.threerings.getdown.tools.Patcher; -import com.threerings.getdown.util.ConfigUtil; +import com.threerings.getdown.util.Config; import com.threerings.getdown.util.ConnectionUtil; import com.threerings.getdown.util.FileUtil; import com.threerings.getdown.util.LaunchUtil; @@ -304,9 +304,8 @@ public abstract class Getdown extends Thread File pfile = _app.getLocalPath("proxy.txt"); if (pfile.exists()) { try { - Map pconf = - ConfigUtil.parseConfig(pfile, ConfigUtil.createOpts(false)); - setProxyProperties((String)pconf.get("host"), (String)pconf.get("port")); + Config pconf = Config.parseConfig(pfile, Config.createOpts(false)); + setProxyProperties(pconf.getString("host"), pconf.getString("port")); return true; } catch (IOException ioe) { log.warning("Failed to read '" + pfile + "': " + ioe); diff --git a/src/main/java/com/threerings/getdown/launcher/GetdownAppletConfig.java b/src/main/java/com/threerings/getdown/launcher/GetdownAppletConfig.java index a813ebd..d6544e8 100644 --- a/src/main/java/com/threerings/getdown/launcher/GetdownAppletConfig.java +++ b/src/main/java/com/threerings/getdown/launcher/GetdownAppletConfig.java @@ -25,7 +25,7 @@ import com.samskivert.util.StringUtil; import com.threerings.getdown.data.Application; import com.threerings.getdown.launcher.ImageLoader; import com.threerings.getdown.launcher.RotatingBackgrounds; -import com.threerings.getdown.util.ConfigUtil; +import com.threerings.getdown.util.Config; import static com.threerings.getdown.Log.log; @@ -185,8 +185,8 @@ public class GetdownAppletConfig // This allows us to configure the status panel from applet parameters in case something // goes horribly wrong before we get a chance to read getdown.txt (like when the user // rejects write permission for the applet) - statusBounds = Application.parseRect("ui.status", getParameter("ui.status")); - statusColor = Application.parseColor(getParameter("ui.status_text")); + statusBounds = Config.parseRect("ui.status", getParameter("ui.status")); + statusColor = Config.parseColor(getParameter("ui.status_text")); } /** @@ -366,9 +366,8 @@ public class GetdownAppletConfig boolean createGetdown = !gdfile.exists(); if (!createGetdown) { try { - Map cdata = - ConfigUtil.parseConfig(gdfile, ConfigUtil.createOpts(false)); - String oappbase = StringUtil.trim((String)cdata.get(APPBASE)); + Config config = Config.parseConfig(gdfile, Config.createOpts(false)); + String oappbase = StringUtil.trim(config.getString(APPBASE)); createGetdown = (appbase != null && !appbase.trim().equals(oappbase)); if (createGetdown) { log.warning("Recreating getdown.txt due to appbase mismatch", diff --git a/src/main/java/com/threerings/getdown/util/ConfigUtil.java b/src/main/java/com/threerings/getdown/util/Config.java similarity index 61% rename from src/main/java/com/threerings/getdown/util/ConfigUtil.java rename to src/main/java/com/threerings/getdown/util/Config.java index 3284b26..81eade1 100644 --- a/src/main/java/com/threerings/getdown/util/ConfigUtil.java +++ b/src/main/java/com/threerings/getdown/util/Config.java @@ -5,6 +5,9 @@ package com.threerings.getdown.util; +import java.awt.Color; +import java.awt.Rectangle; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -14,8 +17,10 @@ import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import com.samskivert.util.ArrayUtil; import com.samskivert.util.StringUtil; import static com.threerings.getdown.Log.log; @@ -24,7 +29,7 @@ import static com.threerings.getdown.Log.log; * Parses a file containing key/value pairs and returns a {@link HashMap} with the values. Keys may * be repeated, in which case they will be made to reference an array of values. */ -public class ConfigUtil +public class Config { /** Options that control the {@link #parsePairs} function. */ public static class ParseOpts { @@ -123,6 +128,40 @@ public class ConfigUtil return pairs; } + /** + * Takes a comma-separated String of four integers and returns a rectangle using those ints as + * the its x, y, width, and height. + */ + public static Rectangle parseRect (String name, String value) + { + if (!StringUtil.isBlank(value)) { + int[] v = StringUtil.parseIntArray(value); + if (v != null && v.length == 4) { + return new Rectangle(v[0], v[1], v[2], v[3]); + } + log.warning("Ignoring invalid rect '" + name + "' config '" + value + "'."); + } + return null; + } + + /** + * Parses the given hex color value (e.g. FFFFF) and returns a Color object with that value. + * If the given value is null of not a valid hexadecimal number, this will return null. + */ + public static Color parseColor (String hexValue) + { + if (!StringUtil.isBlank(hexValue)) { + try { + int rgba = Integer.parseInt(hexValue, 16); + boolean hasAlpha = hexValue.length() > 6; + return new Color(rgba, hasAlpha); + } catch (NumberFormatException e) { + log.warning("Ignoring invalid color", "hexValue", hexValue, "exception", e); + } + } + return null; + } + /** * Parses a configuration file containing key/value pairs. The file must be in the UTF-8 * encoding. @@ -130,7 +169,7 @@ public class ConfigUtil * @return a map from keys to values, where a value will be an array of strings if more than * one key/value pair in the config file was associated with the same key. */ - public static Map parseConfig (File source, ParseOpts opts) + public static Config parseConfig (File source, ParseOpts opts) throws IOException { Map data = new HashMap<>(); @@ -161,16 +200,57 @@ public class ConfigUtil return parseConfig(source, opts); } - return data; + return new Config(data); + } + + public Config (Map data) { + _data = data; + } + + /** + * Returns whether {@code name} has a value in this config. + */ + public boolean hasValue (String name) { + return _data.containsKey(name); + } + + /** + * Returns the raw-value for {@code name}. This may be a {@code String}, {@code String[]}, or + * {@code null}. + */ + public Object getRaw (String name) { + return _data.get(name); + } + + /** + * Returns the specified config value as a string, or {@code null}. + */ + public String getString (String name) { + return (String)_data.get(name); + } + + /** + * Returns the specified config value as a string, or {@code def}. + */ + public String getString (String name, String def) { + String value = (String)_data.get(name); + return value == null ? def : value; + } + + /** + * Returns the specified config value as a boolean. + */ + public boolean getBoolean (String name) { + return Boolean.parseBoolean(getString(name)); } /** * Massages a single string into an array and leaves existing array values as is. Simplifies * access to parameters that are expected to be arrays. */ - public static String[] getMultiValue (Map data, String name) + public String[] getMultiValue (String name) { - Object value = data.get(name); + Object value = _data.get(name); if (value instanceof String) { return new String[] { (String)value }; } else { @@ -178,6 +258,72 @@ public class ConfigUtil } } + /** Used to parse rectangle specifications from the config file. */ + public Rectangle getRect (String name, Rectangle def) + { + String value = getString(name); + Rectangle rect = parseRect(name, value); + return (rect == null) ? def : rect; + } + + /** + * Parses and returns the config value for {@code name} as an int. If no value is provided, + * {@code def} is returned. If the value is invalid, a warning is logged and {@code def} is + * returned. + */ + public int getInt (String name, int def) { + String value = getString(name); + try { + return value == null ? def : Integer.parseInt(value); + } catch (Exception e) { + log.warning("Ignoring invalid int '" + name + "' config '" + value + "',"); + return def; + } + } + + /** + * Parses and returns the config value for {@code name} as a long. If no value is provided, + * {@code def} is returned. If the value is invalid, a warning is logged and {@code def} is + * returned. + */ + public long getLong (String name, long def) { + String value = getString(name); + try { + return value == null ? def : Long.parseLong(value); + } catch (Exception e) { + log.warning("Ignoring invalid long '" + name + "' config '" + value + "',"); + return def; + } + } + + /** Used to parse color specifications from the config file. */ + public Color getColor (String name, Color def) + { + String value = getString(name); + Color color = parseColor(value); + return (color == null) ? def : color; + } + + /** Parses a list of strings from the config file. */ + public String[] getList (String name) + { + String value = getString(name); + return (value == null) ? ArrayUtil.EMPTY_STRING : StringUtil.parseStringArray(value); + } + + /** + * Parses a URL from the config file, checking first for a localized version. + */ + public String getUrl (String name, String def) + { + String value = getString(name + "." + Locale.getDefault().getLanguage()); + if (!StringUtil.isBlank(value)) { + return value; + } + value = getString(name); + return StringUtil.isBlank(value) ? def : value; + } + /** * A helper function for {@link #parsePairs(Reader,ParseOpts)}. Qualifiers have the following * form: @@ -214,4 +360,6 @@ public class ConfigUtil String os = bits[0], arch = (bits.length > 1) ? bits[1] : ""; return (osname.indexOf(os) != -1) && (osarch.indexOf(arch) != -1); } + + private final Map _data; } diff --git a/src/test/java/com/threerings/getdown/util/ConfigUtilTest.java b/src/test/java/com/threerings/getdown/util/ConfigTest.java similarity index 89% rename from src/test/java/com/threerings/getdown/util/ConfigUtilTest.java rename to src/test/java/com/threerings/getdown/util/ConfigTest.java index 607bce7..9e9df85 100644 --- a/src/test/java/com/threerings/getdown/util/ConfigUtilTest.java +++ b/src/test/java/com/threerings/getdown/util/ConfigTest.java @@ -15,9 +15,9 @@ import org.junit.*; import static org.junit.Assert.*; /** - * Tests {@link ConfigUtil}. + * Tests {@link Config}. */ -public class ConfigUtilTest +public class ConfigTest { public static class Pair { public final String key; @@ -38,8 +38,8 @@ public class ConfigUtilTest @Test public void testSimplePairs () throws IOException { - List pairs = ConfigUtil.parsePairs( - toReader(SIMPLE_PAIRS), ConfigUtil.createOpts(true)); + List pairs = Config.parsePairs( + toReader(SIMPLE_PAIRS), Config.createOpts(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]); @@ -58,10 +58,10 @@ public class ConfigUtilTest Pair notWin = new Pair("fifteen", "[!windows] sixteen"); Pair[] pairs = { linux, mac, linuxAndMac, linux64, linux64s, mac64, win64, notWin }; - ConfigUtil.ParseOpts opts = ConfigUtil.createOpts(false); + Config.ParseOpts opts = Config.createOpts(false); opts.osname = "linux"; opts.osarch = "i386"; - List parsed = ConfigUtil.parsePairs(toReader(pairs), opts); + List parsed = Config.parsePairs(toReader(pairs), opts); assertTrue(exists(parsed, linux.key)); assertTrue(!exists(parsed, mac.key)); assertTrue(exists(parsed, linuxAndMac.key)); @@ -72,7 +72,7 @@ public class ConfigUtilTest assertTrue(exists(parsed, notWin.key)); opts.osarch = "x86_64"; - parsed = ConfigUtil.parsePairs(toReader(pairs), opts); + parsed = Config.parsePairs(toReader(pairs), opts); assertTrue(exists(parsed, linux.key)); assertTrue(!exists(parsed, mac.key)); assertTrue(exists(parsed, linuxAndMac.key)); @@ -83,7 +83,7 @@ public class ConfigUtilTest assertTrue(exists(parsed, notWin.key)); opts.osarch = "amd64"; - parsed = ConfigUtil.parsePairs(toReader(pairs), opts); + parsed = Config.parsePairs(toReader(pairs), opts); assertTrue(exists(parsed, linux.key)); assertTrue(!exists(parsed, mac.key)); assertTrue(exists(parsed, linuxAndMac.key)); @@ -95,7 +95,7 @@ public class ConfigUtilTest opts.osname = "mac os x"; opts.osarch = "x86_64"; - parsed = ConfigUtil.parsePairs(toReader(pairs), opts); + parsed = Config.parsePairs(toReader(pairs), opts); assertTrue(!exists(parsed, linux.key)); assertTrue(exists(parsed, mac.key)); assertTrue(exists(parsed, linuxAndMac.key)); @@ -107,7 +107,7 @@ public class ConfigUtilTest opts.osname = "windows"; opts.osarch = "i386"; - parsed = ConfigUtil.parsePairs(toReader(pairs), opts); + parsed = Config.parsePairs(toReader(pairs), opts); assertTrue(!exists(parsed, linux.key)); assertTrue(!exists(parsed, mac.key)); assertTrue(!exists(parsed, linuxAndMac.key)); @@ -118,7 +118,7 @@ public class ConfigUtilTest assertTrue(!exists(parsed, notWin.key)); opts.osarch = "x86_64"; - parsed = ConfigUtil.parsePairs(toReader(pairs), opts); + parsed = Config.parsePairs(toReader(pairs), opts); assertTrue(!exists(parsed, linux.key)); assertTrue(!exists(parsed, mac.key)); assertTrue(!exists(parsed, linuxAndMac.key)); @@ -129,7 +129,7 @@ public class ConfigUtilTest assertTrue(!exists(parsed, notWin.key)); opts.osarch = "amd64"; - parsed = ConfigUtil.parsePairs(toReader(pairs), opts); + parsed = Config.parsePairs(toReader(pairs), opts); assertTrue(!exists(parsed, linux.key)); assertTrue(!exists(parsed, mac.key)); assertTrue(!exists(parsed, linuxAndMac.key));