EMMA Coverage Report (generated Sun Apr 20 22:38:01 CEST 2008)
[all classes][net.sf.jomic.tools]

COVERAGE SUMMARY FOR SOURCE FILE [BasicSettings.java]

nameclass, %method, %block, %line, %
BasicSettings.java100% (1/1)94%  (31/33)82%  (825/1011)85%  (169.6/199)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class BasicSettings100% (1/1)94%  (31/33)82%  (825/1011)85%  (169.6/199)
clear (): void 0%   (0/1)0%   (0/11)0%   (0/4)
isValidPropertyName (String): boolean 0%   (0/1)0%   (0/35)0%   (0/11)
applyComponentAreaProperty (String, Component): void 100% (1/1)52%  (93/179)74%  (26/35)
isOutsideOfScreen (String, int, int, int, int): boolean 100% (1/1)77%  (147/190)87%  (26/30)
setFileProperty (String, File): void 100% (1/1)79%  (11/14)80%  (4/5)
<static initializer> 100% (1/1)80%  (12/15)80%  (0.8/1)
BasicSettings (): void 100% (1/1)81%  (21/26)96%  (4.8/5)
addPropertyChangeListener (PropertyChangeListener): void 100% (1/1)100% (5/5)100% (2/2)
addPropertyChangeListener (String, PropertyChangeListener): void 100% (1/1)100% (6/6)100% (2/2)
firePropertyChangeEvent (String, Object, Object): void 100% (1/1)100% (7/7)100% (2/2)
getBooleanProperty (String): boolean 100% (1/1)100% (15/15)100% (5/5)
getChoiceProperty (String, String []): String 100% (1/1)100% (53/53)100% (7/7)
getColorProperty (String): Color 100% (1/1)100% (55/55)100% (13/13)
getFileProperty (String): File 100% (1/1)100% (16/16)100% (5/5)
getIntProperty (String): int 100% (1/1)100% (42/42)100% (8/8)
getLimitedIntProperty (String, int, int): int 100% (1/1)100% (63/63)100% (9/9)
getProperty (String): String 100% (1/1)100% (21/21)100% (8/8)
getProperty (String, String): String 100% (1/1)100% (5/5)100% (1/1)
getSystemPropertyName (String): String 100% (1/1)100% (10/10)100% (1/1)
remove (String): void 100% (1/1)100% (20/20)100% (4/4)
removePropertyChangeListener (PropertyChangeListener): void 100% (1/1)100% (5/5)100% (2/2)
removePropertyChangeListener (String, PropertyChangeListener): void 100% (1/1)100% (6/6)100% (2/2)
setBooleanProperty (String, boolean): void 100% (1/1)100% (9/9)100% (3/3)
setChoiceProperty (String, String, String []): void 100% (1/1)100% (37/37)100% (4/4)
setColorProperty (String, Color): void 100% (1/1)100% (9/9)100% (2/2)
setComponentAreaProperty (String, Component): void 100% (1/1)100% (40/40)100% (6/6)
setDefault (String, String): void 100% (1/1)100% (7/7)100% (2/2)
setDefault (String, boolean): void 100% (1/1)100% (6/6)100% (2/2)
setDefault (String, int): void 100% (1/1)100% (6/6)100% (2/2)
setIntProperty (String, int): void 100% (1/1)100% (7/7)100% (2/2)
setLimitedIntProperty (String, int, int, int): void 100% (1/1)100% (49/49)100% (6/6)
setProperty (String, String): Object 100% (1/1)100% (38/38)100% (6/6)
setSystemPropertyPrefix (String): void 100% (1/1)100% (4/4)100% (2/2)

1// Jomic - a viewer for comic book archives.
2// Copyright (C) 2004-2008 Thomas Aglassinger
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16package net.sf.jomic.tools;
17 
18import java.awt.Color;
19import java.awt.Component;
20import java.awt.Dimension;
21import java.awt.Point;
22import java.awt.Toolkit;
23import java.beans.PropertyChangeListener;
24import java.beans.PropertyChangeSupport;
25import java.io.File;
26import java.text.ParseException;
27import java.util.Properties;
28 
29import net.sf.wraplog.Logger;
30 
31/**
32 *  Extended <code>Properties</code> with additional features.
33 *  <ul>
34 *    <li> Type-safe getters and setters, for example <code>getIntProperty()</code>
35 *    <li> Possibility to listen for changes
36 *    <li> Overridable by end user by setting corresponding system properties at application launch
37 *    (typically using the <code>java -D</code> option)
38 *    <li> Optional default values using <code>setDefault()</code>. This simplifies writting
39 *    spezialised getters and setters conforming to the Java Beans naming conventions with
40 *    predictable default value behavior. (The original <code>Properties</code> allow to specify
41 *    default properties to the constructor, but it is not documented if the can be changed at run
42 *    time.)
43 *  </ul>
44 *
45 *
46 * @author    Thomas Aglassinger
47 * @see       java.util.Properties
48 * @see       java.beans.PropertyChangeSupport
49 */
50public class BasicSettings extends Properties
51{
52    static final String DEFAULT_COLOR = "#000000";
53 
54    /**
55     *  Slack in percent of screen size which part of a window is allowed to be of screen before its
56     *  location and size are adjusted automatically to ensure that it is visible.
57     */
58    static final double SLACK = 0.10;
59 
60    /**
61     *  Minimum number of pixels that must be visible of a window vertically and horizontally before
62     *  its location and size are adjusted automatically to ensure that it is visible.
63     */
64    static final int SNAP = 3;
65    private static final int COMPONENT_AREA_HEIGHT_INDEX = 3;
66    private static final int COMPONENT_AREA_WIDTH_INDEX = 2;
67    private static final int COMPONENT_AREA_X_INDEX = 0;
68    private static final int COMPONENT_AREA_Y_INDEX = 1;
69 
70    /**
71     *  Values needed to specify what area a AWT component covers.
72     */
73    private static final int EXPECTED_COMPONENT_AREA_VALUE_COUNT = 4;
74    private PropertyChangeSupport changes;
75    private Logger logger;
76    private /*@ spec_public @*/ StringTools stringTools;
77    private /*@ spec_public nullable @*/ String systemPropertyPrefix;
78 
79    protected BasicSettings() {
80        super(new Properties());
81        logger = Logger.getLogger(BasicSettings.class);
82        stringTools = StringTools.instance();
83        changes = new PropertyChangeSupport(this);
84    }
85 
86    //@ requires isValidPropertyName(key);
87    public void setBooleanProperty(String key, boolean value) {
88        String textValue = Boolean.toString(value);
89 
90        setProperty(key, textValue);
91    }
92 
93    /**
94     * Set property <code>key</code> to <code>newValue</code>, and notify
95     * all listeners about the change.
96     *
97     * @exception IllegalArgumentException
98     *                if <code>newValue</code> is not within <code>possibleChoices</code>.
99     */
100    //@ requires isValidPropertyName(key);
101    //@ requires stringTools.isSorted(possibleChoices);
102    public void setChoiceProperty(String key, String newValue, String[] possibleChoices) {
103        if (stringTools.equalsAnyOf(possibleChoices, newValue)) {
104            setProperty(key, newValue);
105        } else {
106            throw new IllegalArgumentException(key + ": " + stringTools.sourced(newValue)
107                    + " not in " + stringTools.arrayToString(possibleChoices));
108        }
109    }
110 
111    /**
112     *  Sets property <code>key</code> to a string representation of <code>newColor</code>, and
113     *  notifies all listeners about the change.
114     */
115    //@ requires isValidPropertyName(key);
116    public void setColorProperty(String key, Color newColor) {
117        setProperty(key, stringTools.colorString(newColor));
118    }
119 
120    /**
121     *  Set property <code>propertyName</code> to store the location and dimension of <code>component</code>
122     *  .
123     */
124    //@ requires isValidPropertyName(propertyName);
125    public void setComponentAreaProperty(String propertyName, Component component) {
126        Point location = component.getLocation();
127        Dimension dimension = component.getSize();
128        int[] values = new int[]{
129                location.x, location.y, dimension.width, dimension.height
130                };
131        String value = stringTools.arrayToString(values);
132 
133        setProperty(propertyName, value);
134    }
135 
136    //@ requires isValidPropertyName(propertyName);
137    public void setFileProperty(String propertyName, /*@ nullable @*/ File newValue) {
138        String filePath;
139 
140        if (newValue == null) {
141            filePath = null;
142        } else {
143            filePath = newValue.getAbsolutePath();
144        }
145        setProperty(propertyName, filePath);
146    }
147 
148    //@ requires isValidPropertyName(key);
149    public void setIntProperty(String key, int value) {
150        setProperty(key, Integer.toString(value));
151    }
152 
153    //@ requires isValidPropertyName(key);
154    //@ requires minValue <= maxValue;
155    public void setLimitedIntProperty(String key, int value, int minValue, int maxValue) {
156        if (value < minValue) {
157            throw new IllegalArgumentException(key + ": " + value + " < " + minValue);
158        } else if (value > maxValue) {
159            throw new IllegalArgumentException(key + ": " + value + " > " + maxValue);
160        }
161        setProperty(key, Integer.toString(value));
162    }
163 
164    /**
165     *  Set property with name <code>key</code> to <code>value</code>.
166     */
167    //@ also
168    //@   requires isValidPropertyName(key);
169    public /*@ nullable @*/ Object setProperty(String key, /*@ nullable @*/ String value) {
170        if (logger.isDebugEnabled()) {
171            logger.debug("set property " + key + " to: " + stringTools.sourced(value));
172        }
173        Object oldValue = getProperty(key);
174 
175        super.setProperty(key, value);
176        firePropertyChangeEvent(key, oldValue, value);
177        return oldValue;
178    }
179 
180    //@ requires newSystemPropertyPrefix.length() > 0;
181    //@ requires newSystemPropertyPrefix.endsWith(".");
182    public void setSystemPropertyPrefix(String newSystemPropertyPrefix) {
183        systemPropertyPrefix = newSystemPropertyPrefix;
184    }
185 
186    /**
187     *  Set the default value for property <code>key</code> to <code>value</code>.
188     */
189    //@ requires isValidPropertyName(key);
190    protected void setDefault(String key, /*@ nullable @*/ String value) {
191        defaults.setProperty(key, value);
192    }
193 
194    /**
195     *  Set the default value for integer property <code>key</code> to <code>value</code>.
196     */
197    //@ requires isValidPropertyName(key);
198    protected void setDefault(String key, int value) {
199        setDefault(key, Integer.toString(value));
200    }
201 
202    /**
203     *  Set the default value for boolean property <code>key</code> to <code>value</code>.
204     */
205    //@ requires isValidPropertyName(key);
206    protected void setDefault(String key, boolean value) {
207        setDefault(key, Boolean.toString(value));
208    }
209 
210    //@ requires isValidPropertyName(key);
211    public /*@ pure @*/ boolean getBooleanProperty(String key) {
212        boolean result;
213        String value = getProperty(key);
214 
215        if (value != null) {
216            result = Boolean.valueOf(value).booleanValue();
217        } else {
218            result = false;
219        }
220        return result;
221    }
222 
223    //@ requires isValidPropertyName(propertyName);
224    //@ requires stringTools.isSorted(possibleChoices);
225    public String getChoiceProperty(String propertyName, String[] possibleChoices) {
226        String result = getProperty(propertyName);
227 
228        if (!stringTools.equalsAnyOf(possibleChoices, result)) {
229            String fixedResult = defaults.getProperty(propertyName, possibleChoices[0]);
230 
231            logger.warn("resetting " + propertyName + " from " + result + " to " + fixedResult
232                    + " because it is not one of " + stringTools.arrayToString(possibleChoices));
233            super.setProperty(propertyName, fixedResult);
234            result = fixedResult;
235        }
236 
237        return result;
238    }
239    //@ requires isValidPropertyName(propertyName);
240    public /*@ nullable pure @*/ Color getColorProperty(String propertyName) {
241        Color result = null;
242        String value = getProperty(propertyName);
243        boolean useDefaultColor = (value == null);
244 
245        if (!useDefaultColor) {
246            try {
247                result = Color.decode(value);
248            } catch (NumberFormatException error) {
249                useDefaultColor = true;
250            }
251        }
252 
253        if (useDefaultColor) {
254            String defaultValue = defaults.getProperty(propertyName, DEFAULT_COLOR);
255 
256            logger.warn(
257                    "property "
258                    + propertyName
259                    + ": using default \""
260                    + defaultValue
261                    + "\" because value \""
262                    + value
263                    + "\" is not a color of the form \"#rrggbb\"");
264            result = Color.decode(defaultValue);
265        }
266 
267        return result;
268    }
269 
270    //@ requires isValidPropertyName(propertyName);
271    public /*@ nullable pure @*/ File getFileProperty(String propertyName) {
272        File result;
273        String value = getProperty(propertyName);
274 
275        if (value == null) {
276            result = null;
277        } else {
278            result = new File(value);
279        }
280        return result;
281    }
282 
283    //@ requires isValidPropertyName(propertyName);
284    public /*@ pure @*/ int getIntProperty(String propertyName) {
285        int result;
286        String value = getProperty(propertyName);
287 
288        try {
289            result = Integer.parseInt(value);
290        } catch (NumberFormatException error) {
291            String defaultValue = defaults.getProperty(propertyName, Integer.toString(0));
292 
293            logger.warn(
294                    "property "
295                    + propertyName
296                    + ": using default "
297                    + defaultValue
298                    + " because value \""
299                    + value
300                    + "\" is not an integer");
301            result = Integer.parseInt(defaultValue);
302        }
303        return result;
304    }
305 
306    /**
307     * Get property <code>propertyName</code> and make sure that it is within
308     * <code>minValue</code> and <code>maxValue</code>. If it is not, fix
309     * it by resetting it to its default. If there is no default for this
310     * property, reset it to the average of <code>minValue</code> and
311     * <code>maxValue</code>.
312     */
313    //@ requires isValidPropertyName(propertyName);
314    //@ requires minValue <= maxValue;
315    //@ ensures \result >= minValue;
316    //@ ensures \result <= maxValue;
317    public int getLimitedIntProperty(String propertyName, int minValue, int maxValue) {
318        int result = getIntProperty(propertyName);
319        if ((result < minValue) || (result > maxValue)) {
320            int averageValue = (minValue + maxValue) / 2;
321            String defaultValue = defaults.getProperty(propertyName, Integer.toString(averageValue));
322            int fixedResult = Integer.parseInt(defaultValue);
323 
324            logger.warn("resetting " + propertyName + " from " + result + " to " + fixedResult
325                    + " because it is not within a range of " + minValue + "..." + maxValue);
326            result = fixedResult;
327            setLimitedIntProperty(propertyName, result, minValue, maxValue);
328        }
329        return result;
330    }
331 
332    /**
333     * @deprecated    use setDefault() to specify a default value
334     * @see           #setDefault(String, String)
335     */
336    public /*@ nullable pure @*/ String getProperty(String name, String defaultValue) {
337        throw new IllegalStateException("defaultValue must be stored using setDefault()");
338    }
339 
340    //@ also
341    //@   requires isValidPropertyName(name);
342    public /*@ nullable pure @*/ String getProperty(String name) {
343        String result;
344 
345        if (systemPropertyPrefix != null) {
346            String systemPropertyName = getSystemPropertyName(name);
347 
348            result = System.getProperty(systemPropertyName);
349        } else {
350            result = null;
351        }
352 
353        if (result == null) {
354            result = super.getProperty(name);
355        }
356        return result;
357    }
358 
359    /**
360     *  Get name of system property corresponding to application property <code>name</code>.
361     */
362    //@ requires isValidPropertyName(name);
363    //@ requires systemPropertyPrefix != null;
364    public /*@ pure @*/ String getSystemPropertyName(String name) {
365        return systemPropertyPrefix + name;
366    }
367 
368    public void addPropertyChangeListener(PropertyChangeListener listener) {
369        changes.addPropertyChangeListener(listener);
370    }
371 
372    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
373        changes.addPropertyChangeListener(propertyName, listener);
374    }
375 
376    /**
377     * Yields <code>true</code> if the specified window coordinates and size are outside of the
378     * screen (given some slack).
379     */
380    private boolean isOutsideOfScreen(String propertyName, int x, int y, int width, int height) {
381        boolean result = false;
382        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
383        int screenWidth = screenSize.width;
384        int screenHeight = screenSize.height;
385        double slackX = screenWidth * SLACK;
386        int minX = 0 - (int) slackX;
387        int maxX = screenWidth - SNAP;
388        int minY = 0;
389        int maxY = screenHeight - SNAP;
390        int maxWidth = screenWidth + SNAP;
391        int maxHeight = screenHeight + SNAP;
392 
393        if (x < minX) {
394            logger.warn(propertyName + ".x is too small, resetting (" + x + " < " + minX + ")");
395            result = true;
396        } else if (x > maxX) {
397            logger.warn(propertyName + ".x is too large, resetting (" + x + " > " + maxX + ")");
398            result = true;
399        } else if (y < minY) {
400            logger.warn(propertyName + ".y is too small, resetting (" + y + " < " + minY + ")");
401            result = true;
402        } else if (y > maxY) {
403            logger.warn(propertyName + ".y is too large, resetting (" + y + " > " + maxY + ")");
404            result = true;
405        } else if (width > maxWidth) {
406            logger.warn(propertyName + ".width is too large, resetting ("
407                    + width + " > " + maxWidth + ")");
408            result = true;
409        } else if (height > maxHeight) {
410            logger.warn(propertyName + ".height is too large, resetting ("
411                    + height + " > " + maxHeight + ")");
412            result = true;
413        }
414        return result;
415    }
416 
417    /**
418     *  Apply the component location and dimension stored in <code>propertyName</code> on <code>component</code>
419     *  .
420     */
421    //@ requires isValidPropertyName(propertyName);
422    public void applyComponentAreaProperty(String propertyName, Component component) {
423        String value = getProperty(propertyName);
424 
425        if (value != null) {
426            try {
427                int[] intValues = stringTools.stringToIntArray(value);
428                int actualComponentAreaValueCount = intValues.length;
429 
430                if (actualComponentAreaValueCount == EXPECTED_COMPONENT_AREA_VALUE_COUNT) {
431                    int x = intValues[COMPONENT_AREA_X_INDEX];
432                    int y = intValues[COMPONENT_AREA_Y_INDEX];
433                    int width = intValues[COMPONENT_AREA_WIDTH_INDEX];
434                    int height = intValues[COMPONENT_AREA_HEIGHT_INDEX];
435 
436                    // If area is outside of screen (given some slack), reset area
437                    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
438                    int screenWidth = screenSize.width;
439                    int screenHeight = screenSize.height;
440                    boolean reset = isOutsideOfScreen(propertyName, x, y, width, height);
441 
442                    if (reset) {
443                        // Attempt to reset to default value
444                        remove(propertyName);
445 
446                        String defaultValue = getProperty(propertyName);
447 
448                        if (defaultValue == null) {
449                            // No default, figure out some area that is guaranteed to be visible
450                            // (Component does not have pack())
451                            width = screenWidth / 2;
452                            height = screenHeight / 2;
453                            x = (screenWidth - width) / 2;
454                            y = (screenHeight - height) / 2;
455                        } else {
456                            // Use the default value
457                            int[] intDefaultValues = stringTools.stringToIntArray(defaultValue);
458 
459                            assert intDefaultValues.length == EXPECTED_COMPONENT_AREA_VALUE_COUNT
460                                    : "default value for property " + propertyName + " must be fixed: "
461                                    + stringTools.sourced(defaultValue);
462                            x = intDefaultValues[COMPONENT_AREA_X_INDEX];
463                            y = intDefaultValues[COMPONENT_AREA_Y_INDEX];
464                            width = intDefaultValues[COMPONENT_AREA_WIDTH_INDEX];
465                            height = intDefaultValues[COMPONENT_AREA_HEIGHT_INDEX];
466                        }
467                    }
468 
469                    component.setLocation(x, y);
470                    component.setSize(width, height);
471                } else {
472                    // No need to localize because this message only shows up in the log (see "catch" below).
473                    throw new ParseException(
474                            "component area must be specified using " + EXPECTED_COMPONENT_AREA_VALUE_COUNT
475                            + " instead of " + actualComponentAreaValueCount + " items", 0);
476                }
477            } catch (Exception error) {
478                logger.warn("cannot use value of property " + stringTools.sourced(propertyName)
479                        + " to apply on component location and dimension: "
480                        + stringTools.sourced(getProperty(propertyName)), error);
481            }
482        }
483    }
484 
485    /**
486     *  Reset all settings to their default value.
487     */
488    public void clear() {
489        if (logger.isInfoEnabled()) {
490            logger.info("reset settings to default values");
491        }
492        super.clear();
493    }
494 
495    /**
496     *  Remove the property <code>key</code>. This either resets it to the internal default, or the
497     *  value optionally specified in the corresponding system property.
498     */
499    //@ requires isValidPropertyName(key);
500    public void remove(String key) {
501        if (logger.isDebugEnabled()) {
502            logger.debug("remove property " + key);
503        }
504        super.remove(key);
505    }
506 
507    public void removePropertyChangeListener(PropertyChangeListener listener) {
508        changes.removePropertyChangeListener(listener);
509    }
510 
511    //@ requires isValidPropertyName(propertyName);
512    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
513        changes.removePropertyChangeListener(propertyName, listener);
514    }
515 
516    /**
517     * <code>True</code> if <code>propertyName</code> is valid. Invalid names cause an error
518     * message to be logged that explains why the name was rejected.
519     */
520    protected /*@ spec_public pure @*/ boolean isValidPropertyName(/*@ nullable @*/ String propertyName) {
521        boolean result = true;
522 
523        if (propertyName == null) {
524            logger.error("propertyName is null");
525            result = false;
526        } else if (propertyName.length() == 0) {
527            logger.error("propertyName is empty");
528            result = false;
529        } else if (!propertyName.trim().equals(propertyName)) {
530            logger.error("propertyName contains leading or trailing white space: \"{}\"", propertyName);
531            result = false;
532        }
533        return  result;
534    }
535    /**
536     *  Notify all listeners that have registered interest for notification on this event type. The
537     *  event instance is lazily created using the parameters passed into the fire method.
538     */
539    //@ requires isValidPropertyName(propertyName);
540    protected void firePropertyChangeEvent(String propertyName, /*@ nullable @*/ Object oldValue,
541            /*@ nullable @*/ Object newValue) {
542        changes.firePropertyChange(propertyName, oldValue, newValue);
543    }
544}

[all classes][net.sf.jomic.tools]
EMMA 2.0.4217 (C) Vladimir Roubtsov