Merge pull request #134 from sdgx/unsafe-public-fields

Unsafe public fields in UpdateInterface class: make public fields final
This commit is contained in:
Michael Bayne
2018-09-05 14:44:22 -07:00
committed by GitHub
9 changed files with 331 additions and 97 deletions
@@ -5,8 +5,6 @@
package com.threerings.getdown.data;
import java.awt.Color;
import java.awt.Rectangle;
import java.io.*;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
@@ -57,7 +55,7 @@ public class Application
public static final String MANIFEST_CLASS = "manifest";
/** Used to communicate information about the UI displayed when updating the application. */
public static class UpdateInterface
public static final class UpdateInterface
{
/**
* The major steps involved in updating, along with some arbitrary percentages
@@ -85,68 +83,67 @@ public class Application
}
/** The human readable name of this application. */
public String name;
public final String name;
/** A background color, just in case. */
public Color background = Color.white;
public final Color background;
/** Background image specifiers for `RotatingBackgrounds`. */
public String[] rotatingBackgrounds;
public final List<String> rotatingBackgrounds;
/** The error background image for `RotatingBackgrounds`. */
public String errorBackground;
public final String errorBackground;
/** The paths (relative to the appdir) of images for the window icon. */
public String[] iconImages;
public final List<String> iconImages;
/** The path (relative to the appdir) to a single background image. */
public String backgroundImage;
public final String backgroundImage;
/** The path (relative to the appdir) to the progress bar image. */
public String progressImage;
public final String progressImage;
/** The dimensions of the progress bar. */
public Rectangle progress = new Rectangle(5, 5, 300, 15);
public final Rectangle progress;
/** The color of the progress text. */
public Color progressText = Color.black;
public final Color progressText;
/** The color of the progress bar. */
public Color progressBar = new Color(0x6699CC);
public final Color progressBar;
/** The dimensions of the status display. */
public Rectangle status = new Rectangle(5, 25, 500, 100);
public final Rectangle status;
/** The color of the status text. */
public Color statusText = Color.black;
public final Color statusText;
/** The color of the text shadow. */
public Color textShadow;
public final Color textShadow;
/** Where to point the user for help with install errors. */
public String installError;
public final String installError;
/** The dimensions of the patch notes button. */
public Rectangle patchNotes = new Rectangle(5, 50, 112, 26);
public final Rectangle patchNotes;
/** The patch notes URL. */
public String patchNotesUrl;
public final String patchNotesUrl;
/** Whether window decorations are hidden for the UI. */
public boolean hideDecorations;
public final boolean hideDecorations;
/** Whether progress text should be hidden or not. */
public boolean hideProgressText;
public final boolean hideProgressText;
/** The minimum number of seconds to display the GUI. This is to prevent the GUI from
* flashing up on the screen and immediately disappearing, which can be confusing to the
* user. */
public int minShowSeconds = 5;
public final int minShowSeconds;
/** The global percentages for each step. A step may have more than one, and
* the lowest reasonable one is used if a step is revisited. */
public Map<Step, List<Integer>> stepPercentages =
new EnumMap<Step, List<Integer>>(Step.class);
public final Map<Step, List<Integer>> stepPercentages;
/** Generates a string representation of this instance. */
@Override
@@ -160,11 +157,56 @@ public class Application
", hideProgressText" + hideProgressText + ", minShow=" + minShowSeconds + "]";
}
/** Initializer */
public UpdateInterface (Config config)
{
this.name = config.getString("ui.name");
this.progress = config.getRect("ui.progress", new Rectangle(5, 5, 300, 15));
this.progressText = config.getColor("ui.progress_text", Color.BLACK);
this.hideProgressText = config.getBoolean("ui.hide_progress_text");
this.minShowSeconds = config.getInt("ui.min_show_seconds", 5);
this.progressBar = config.getColor("ui.progress_bar", new Color(0x66, 0x99, 0xCC));
this.status = config.getRect("ui.status", new Rectangle(5, 25, 500, 100));
this.statusText = config.getColor("ui.status_text", Color.BLACK);
this.textShadow = config.getColor("ui.text_shadow", null);
this.hideDecorations = config.getBoolean("ui.hide_decorations");
this.backgroundImage = config.getString("ui.background_image",
config.getString("ui.background")); // support legacy format
// and now ui.background can refer to the background color, but fall back to black
// or white, depending on the brightness of the progressText
Color defaultBackground = (.5f < this.progressText.brightness())
? Color.BLACK
: Color.WHITE;
this.background = config.getColor("ui.background", defaultBackground);
this.progressImage = config.getString("ui.progress_image");
this.rotatingBackgrounds = stringsToList(config.getMultiValue("ui.rotating_background"));
this.iconImages = stringsToList(config.getMultiValue("ui.icon"));
this.errorBackground = config.getString("ui.error_background");
// On an installation error, where do we point the user.
String installError = config.getUrl("ui.install_error", null);
this.installError = (installError == null) ?
"m.default_install_error" : MessageUtil.taint(installError);
// the patch notes bits
this.patchNotes = config.getRect("ui.patch_notes", new Rectangle(5, 50, 112, 26));
this.patchNotesUrl = config.getUrl("ui.patch_notes_url", null);
// step progress percentage (defaults and then customized values)
EnumMap<Step, List<Integer>> stepPercentages = new EnumMap<Step, List<Integer>>(Step.class);
for (Step step : Step.values()) {
stepPercentages.put(step, step.defaultPercents);
}
for (UpdateInterface.Step step : UpdateInterface.Step.values()) {
String spec = config.getString("ui.percents." + step.name());
if (spec != null) {
try {
stepPercentages.put(step, intsToList(StringUtil.parseIntArray(spec)));
} catch (Exception e) {
log.warning("Failed to parse percentages for " + step + ": " + spec);
}
}
}
this.stepPercentages = Collections.unmodifiableMap(stepPercentages);
}
}
@@ -696,59 +738,13 @@ public class Application
_codeCacheRetentionDays = config.getInt("code_cache_retention_days", 7);
// parse and return our application config
UpdateInterface ui = new UpdateInterface();
_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 = 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
Color defaultBackground = (.5f < Color.RGBtoHSB(
ui.progressText.getRed(), ui.progressText.getGreen(), ui.progressText.getBlue(),
null)[2])
? Color.BLACK
: Color.WHITE;
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");
UpdateInterface ui = new UpdateInterface(config);
_name = ui.name;
_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 = config.getUrl("ui.install_error", null);
ui.installError = (installError == null) ?
"m.default_install_error" : MessageUtil.taint(installError);
// the patch notes bits
ui.patchNotes = config.getRect("ui.patch_notes", ui.patchNotes);
ui.patchNotesUrl = config.getUrl("ui.patch_notes_url", null);
// step progress percentages
for (UpdateInterface.Step step : UpdateInterface.Step.values()) {
String spec = config.getString("ui.percents." + step.name());
if (spec != null) {
try {
ui.stepPercentages.put(step, intsToList(StringUtil.parseIntArray(spec)));
} catch (Exception e) {
log.warning("Failed to parse percentages for " + step + ": " + spec);
}
}
}
return ui;
}
@@ -1639,6 +1635,18 @@ public class Application
return Collections.unmodifiableList(list);
}
/**
* Make an immutable List from the specified String array.
*/
public static List<String> stringsToList (String[] values)
{
List<String> list = new ArrayList<>(values.length);
for (String val : values) {
list.add(val);
}
return Collections.unmodifiableList(list);
}
/** Used to parse resources with the specified name. */
protected void parseResources (Config config, String name, EnumSet<Resource.Attr> attrs,
List<Resource> list)
@@ -0,0 +1,81 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.util;
/**
* An immutable color.
*/
public class Color
{
public final static Color WHITE = new Color(255, 255, 255);
public final static Color BLACK = new Color(0, 0, 0);
public final int red;
public final int green;
public final int blue;
public final int alpha;
public Color (int r, int g, int b)
{
this.red = Math.min(Math.max(r, 0), 255);
this.green = Math.min(Math.max(g, 0), 255);
this.blue = Math.min(Math.max(b, 0), 255);
this.alpha = 255;
}
public Color (int rgba, boolean hasAlpha)
{
this.red = (rgba >> 16) & 0xFF;
this.green = (rgba >> 8) & 0xFF;
this.blue = (rgba >> 0) & 0xFF;
if (hasAlpha) {
this.alpha = (rgba >> 24) & 0xFF;
} else {
this.alpha = 255;
}
}
/**
* Returns the combined RBGA value for this color.
*/
public int rgba ()
{
return ((alpha & 0xFF) << 24) |
((red & 0xFF) << 16) |
((green & 0xFF) << 8) |
((blue & 0xFF) << 0);
}
/**
* Returns the brightness of this color, per the standard HSB model.
*/
public float brightness ()
{
int max = Math.max(Math.max(red, green), blue);
return ((float) max) / 255.0f;
}
/** {@inheritDoc} */
@Override
public boolean equals(Object other)
{
return other instanceof Color && ((Color)other).rgba() == this.rgba();
}
/** {@inheritDoc} */
@Override
public int hashCode()
{
return rgba();
}
/** {@inheritDoc} */
public String toString ()
{
return getClass().getName() + "[r=" + red + ", g=" + green + ", b=" + blue + ", a=" + alpha + "]";
}
}
@@ -5,9 +5,6 @@
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;
@@ -32,6 +29,9 @@ import static com.threerings.getdown.Log.log;
*/
public class Config
{
/** Empty configuration. */
public static final Config EMPTY = new Config(new HashMap<String, Object>());
/** Options that control the {@link #parsePairs} function. */
public static class ParseOpts {
// these should be tweaked as desired by the caller
@@ -0,0 +1,31 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.util;
/**
* An immutable rectangle.
*/
public class Rectangle
{
public final int x;
public final int y;
public final int width;
public final int height;
public Rectangle (int x, int y, int width, int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/** {@inheritDoc} */
public String toString ()
{
return getClass().getName() + "[x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]";
}
}
@@ -0,0 +1,111 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.util;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/**
* Tests {@link Color}.
*/
public class ColorTest
{
@Test
public void testRgbConstructor ()
{
Color color1 = new Color(0, 0, 0);
assertEquals(0, color1.red);
assertEquals(0, color1.green);
assertEquals(0, color1.blue);
assertEquals(255, color1.alpha);
assertEquals(0xFF000000, color1.rgba());
assertEquals(0, color1.brightness(), 0.0000001);
assertEquals(Color.BLACK, color1);
Color color2 = new Color(255, 255, 255);
assertEquals(255, color2.red);
assertEquals(255, color2.green);
assertEquals(255, color2.blue);
assertEquals(255, color2.alpha);
assertEquals(0xFFFFFFFF, color2.rgba());
assertEquals(1, color2.brightness(), 0.0000001);
assertEquals(Color.WHITE, color2);
Color color3 = new Color(1, 2, 3);
assertEquals(1, color3.red);
assertEquals(2, color3.green);
assertEquals(3, color3.blue);
assertEquals(255, color3.alpha);
assertEquals(0xFF010203, color3.rgba());
assertEquals(0.0117647, color3.brightness(), 0.0000001);
Color color4 = new Color(-100, 300, 200);
assertEquals(0, color4.red);
assertEquals(255, color4.green);
assertEquals(200, color4.blue);
assertEquals(255, color4.alpha);
assertEquals(0xFF00FFC8, color4.rgba());
assertEquals(1, color4.brightness(), 0.0000001);
}
@Test
public void testRgbaConstructor ()
{
Color color1 = new Color(0, false);
assertEquals(0, color1.red);
assertEquals(0, color1.green);
assertEquals(0, color1.blue);
assertEquals(255, color1.alpha);
assertEquals(0xFF000000, color1.rgba());
assertEquals(0, color1.brightness(), 0.0000001);
assertEquals(Color.BLACK, color1);
Color color2 = new Color(0, true);
assertEquals(0, color2.red);
assertEquals(0, color2.green);
assertEquals(0, color2.blue);
assertEquals(0, color2.alpha);
assertEquals(0, color2.rgba());
assertEquals(0, color2.brightness(), 0.0000001);
assertFalse(Color.BLACK.equals(color2));
Color color3 = new Color(0x00FFFFFF, false);
assertEquals(255, color3.red);
assertEquals(255, color3.green);
assertEquals(255, color3.blue);
assertEquals(255, color3.alpha);
assertEquals(0xFFFFFFFF, color3.rgba());
assertEquals(1, color3.brightness(), 0.0000001);
assertEquals(Color.WHITE, color3);
Color color4 = new Color(0x00FFFFFF, true);
assertEquals(255, color4.red);
assertEquals(255, color4.green);
assertEquals(255, color4.blue);
assertEquals(0, color4.alpha);
assertEquals(0x00FFFFFF, color4.rgba());
assertEquals(1, color4.brightness(), 0.0000001);
assertFalse(Color.WHITE.equals(color4));
Color color5 = new Color(0x00010203, false);
assertEquals(1, color5.red);
assertEquals(2, color5.green);
assertEquals(3, color5.blue);
assertEquals(255, color5.alpha);
assertEquals(0xFF010203, color5.rgba());
assertEquals(0.0117647, color5.brightness(), 0.0000001);
Color color6 = new Color(0x00010203, true);
assertEquals(1, color6.red);
assertEquals(2, color6.green);
assertEquals(3, color6.blue);
assertEquals(0, color6.alpha);
assertEquals(0x00010203, color6.rgba());
assertEquals(0.0117647, color6.brightness(), 0.0000001);
}
}
@@ -7,17 +7,15 @@ package com.threerings.getdown.launcher;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
@@ -890,7 +888,9 @@ public abstract class Getdown extends Thread
_status.setSize(size);
_layers.setPreferredSize(size);
_patchNotes.setBounds(_ifc.patchNotes);
Rectangle patchNotes = new Rectangle(_ifc.patchNotes.x, _ifc.patchNotes.y,
_ifc.patchNotes.width, _ifc.patchNotes.height);
_patchNotes.setBounds(patchNotes);
_patchNotes.setVisible(false);
// we were displaying progress while the UI wasn't up. Now that it is, whatever progress
@@ -1131,7 +1131,7 @@ public abstract class Getdown extends Thread
};
protected Application _app;
protected Application.UpdateInterface _ifc = new Application.UpdateInterface();
protected Application.UpdateInterface _ifc = new Application.UpdateInterface(Config.EMPTY);
protected ResourceBundle _msgs;
protected Container _container;
@@ -142,7 +142,7 @@ public class GetdownApp
});
_frame.setUndecorated(_ifc.hideDecorations);
try {
_frame.setBackground(_ifc.background);
_frame.setBackground(new Color(_ifc.background.rgba(), true));
} catch (UnsupportedOperationException e) {
log.warning("Failed to set background", e);
} catch (IllegalComponentStateException e) {
@@ -6,6 +6,7 @@
package com.threerings.getdown.launcher;
import java.awt.Image;
import java.util.List;
import static com.threerings.getdown.Log.log;
@@ -42,15 +43,15 @@ public class RotatingBackgrounds
* time when the next should be shown. In that case, it's left up until its been there for its
* minimum display time and then the next one gets to come up.
*/
public RotatingBackgrounds (String[] backgrounds, String errorBackground, ImageLoader loader)
public RotatingBackgrounds (List<String> backgrounds, String errorBackground, ImageLoader loader)
{
percentages = new int[backgrounds.length];
minDisplayTime = new int[backgrounds.length];
images = new Image[backgrounds.length];
for (int ii = 0; ii < backgrounds.length; ii++) {
String[] pieces = backgrounds[ii].split(";");
percentages = new int[backgrounds.size()];
minDisplayTime = new int[backgrounds.size()];
images = new Image[backgrounds.size()];
for (int ii = 0; ii < backgrounds.size(); ii++) {
String[] pieces = backgrounds.get(ii).split(";");
if (pieces.length != 2) {
log.warning("Unable to parse background image '" + backgrounds[ii] + "'");
log.warning("Unable to parse background image '" + backgrounds.get(ii) + "'");
makeEmpty();
return;
}
@@ -59,11 +60,11 @@ public class RotatingBackgrounds
minDisplayTime[ii] = Integer.parseInt(pieces[1]);
} catch (NumberFormatException e) {
log.warning("Unable to parse background image display time '" +
backgrounds[ii] + "'");
backgrounds.get(ii) + "'");
makeEmpty();
return;
}
percentages[ii] = (int)((ii/(float)backgrounds.length) * 100);
percentages[ii] = (int)((ii/(float)backgrounds.size()) * 100);
}
if (errorBackground == null) {
errorImage = images[0];
@@ -64,7 +64,9 @@ public class StatusPanel extends JComponent
int width = img == null ? -1 : img.getWidth(this);
int height = img == null ? -1 : img.getHeight(this);
if (width == -1 || height == -1) {
Rectangle bounds = ifc.progress.union(ifc.status);
Rectangle progress = new Rectangle(ifc.progress.x, ifc.progress.y, ifc.progress.width, ifc.progress.height);
Rectangle status = new Rectangle(ifc.status.x, ifc.status.y, ifc.status.width, ifc.status.height);
Rectangle bounds = progress.union(status);
// assume the x inset defines the frame padding; add it on the left, right, and bottom
_psize = new Dimension(bounds.x + bounds.width + bounds.x,
bounds.y + bounds.height + bounds.x);
@@ -107,7 +109,7 @@ public class StatusPanel extends JComponent
_progress = percent;
if (!_ifc.hideProgressText) {
String msg = MessageFormat.format(get("m.complete"), percent);
_newplab = createLabel(msg, _ifc.progressText);
_newplab = createLabel(msg, new Color(_ifc.progressText.rgba(), true));
}
needsRepaint = true;
}
@@ -132,7 +134,7 @@ public class StatusPanel extends JComponent
int minutes = (int)(remaining / 60), seconds = (int)(remaining % 60);
String remstr = minutes + ":" + ((seconds < 10) ? "0" : "") + seconds;
String msg = MessageFormat.format(get("m.remain"), remstr);
_newrlab = createLabel(msg, _ifc.statusText);
_newrlab = createLabel(msg, new Color(_ifc.statusText.rgba(), true));
}
needsRepaint = true;
@@ -226,7 +228,7 @@ public class StatusPanel extends JComponent
gfx.drawImage(_barimg, _ifc.progress.x, _ifc.progress.y, null);
gfx.setClip(null);
} else {
gfx.setColor(_ifc.progressBar);
gfx.setColor(new Color(_ifc.progressBar.rgba(), true));
gfx.fillRect(_ifc.progress.x, _ifc.progress.y,
_progress * _ifc.progress.width / 100,
_ifc.progress.height);
@@ -270,7 +272,7 @@ public class StatusPanel extends JComponent
status += " .";
}
}
_newlab = createLabel(status, _ifc.statusText);
_newlab = createLabel(status, new Color(_ifc.statusText.rgba(), true));
// set the width of the label to the width specified
int width = _ifc.status.width;
if (width == 0) {
@@ -305,7 +307,7 @@ public class StatusPanel extends JComponent
{
Label label = new Label(text, color, FONT);
if (_ifc.textShadow != null) {
label.setAlternateColor(_ifc.textShadow);
label.setAlternateColor(new Color(_ifc.textShadow.rgba(), true));
label.setStyle(LabelStyleConstants.SHADOW);
}
return label;