1 /******************************************************************************* 2 * Copyright (c) 2000, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.core.runtime; 15 16 import java.io.*; 17 import java.util.*; 18 import org.eclipse.core.internal.preferences.PreferencesService; 19 import org.eclipse.core.internal.preferences.PrefsMessages; 20 import org.eclipse.core.runtime.preferences.*; 21 import org.eclipse.osgi.util.NLS; 22 23 /** 24 * A table of preference settings, mapping named properties to values. Property 25 * names are non-empty strings; property values can be either booleans, 26 * non-null strings, or values of one of the primitive number types. 27 * The table consists of two, sparse, layers: the lower layer holds default values 28 * for properties, and the upper layer holds explicitly set values for properties. 29 * Normal retrieval looks for an explicitly set value for the given property in 30 * the upper layer; if there is nothing for that property in the upper layer, it 31 * next looks for a default value for the given property in the lower layer; if 32 * there is nothing for that property in the lower layer, it returns a standard 33 * default-default value. The default-default values for the primitive types are 34 * as follows: 35 * <ul> 36 * <li><code>boolean</code> = <code>false</code></li> 37 * <li><code>double</code> = <code>0.0</code></li> 38 * <li><code>float</code> = <code>0.0f</code></li> 39 * <li><code>int</code> = <code>0</code></li> 40 * <li><code>long</code> = <code>0L</code></li> 41 * <li><code>String</code> = <code>""</code> (the empty string)</li> 42 * </ul> 43 * <p> 44 * Internally, all properties values (in both layers) are stored as strings. 45 * Standard conversions to and from numeric and boolean types are performed on 46 * demand. 47 * </p> 48 * <p> 49 * The typical usage is to establish the defaults for all known properties 50 * and then restore previously stored values for properties whose values 51 * were explicitly set. The existing settings can be changed and new properties 52 * can be set (<code>setValue</code>). If the values specified is the same as 53 * the default value, the explicit setting is deleted from the top layer. 54 * It is also possible to reset a property value back to the default value 55 * using <code>setToDefault</code>. After the properties have been modified, 56 * the properties with explicit settings are written to disk. The default values 57 * are never saved. This two-tiered approach 58 * to saving and restoring property setting minimizes the number of properties 59 * that need to be persisted; indeed, the normal starting state does not require 60 * storing any properties at all. It also makes it easy to use different 61 * default settings in different environments while maintaining just those 62 * property settings the user has adjusted. 63 * </p> 64 * <p> 65 * A property change event is reported whenever a property's value actually 66 * changes (either through <code>setValue</code>, <code>setToDefault</code>). 67 * Note, however, that manipulating default values (with <code>setDefault</code>) 68 * does not cause any events to be reported. 69 * </p> 70 * <p> 71 * Clients may instantiate this class. 72 * </p> 73 * <p> 74 * The implementation is based on a pair of internal 75 * <code>java.util.Properties</code> objects, one holding explicitly set values 76 * (set using <code>setValue</code>), the other holding the default values 77 * (set using <code>setDefaultValue</code>). The <code>load</code> and 78 * <code>store</code> methods persist the non-default property values to 79 * streams (the default values are not saved). 80 * </p> 81 * <p> 82 * If a client sets a default value to be equivalent to the default-default for that 83 * type, the value is still known to the preference store as having a default value. 84 * That is, the name will still be returned in the result of the <code>defaultPropertyNames</code> 85 * and <code>contains</code> methods. 86 * </p> 87 * 88 * @since 2.0 89 * @noextend This class is not intended to be subclassed by clients. 90 * @deprecated This class is replaced by {@link IEclipsePreferences}. Setting a default 91 * value is accomplished by a setting a value in the {@link DefaultScope}, and setting 92 * an explicit non-default value is accomplished by setting a value in the {@link InstanceScope}. 93 * To obtain a preference value, use the preference accessor methods on {@link IPreferencesService}. 94 */ 95 @Deprecated 96 public class Preferences { 97 98 /** 99 * The default-default value for boolean properties (<code>false</code>). 100 */ 101 public static final boolean BOOLEAN_DEFAULT_DEFAULT = false; 102 103 /** 104 * The default-default value for double properties (<code>0.0</code>). 105 */ 106 public static final double DOUBLE_DEFAULT_DEFAULT = 0.0; 107 108 /** 109 * The default-default value for float properties (<code>0.0f</code>). 110 */ 111 public static final float FLOAT_DEFAULT_DEFAULT = 0.0f; 112 113 /** 114 * The default-default value for int properties (<code>0</code>). 115 */ 116 public static final int INT_DEFAULT_DEFAULT = 0; 117 118 /** 119 * The default-default value for long properties (<code>0L</code>). 120 */ 121 public static final long LONG_DEFAULT_DEFAULT = 0L; 122 123 /** 124 * The default-default value for String properties (<code>""</code>). 125 */ 126 public static final String STRING_DEFAULT_DEFAULT = ""; //$NON-NLS-1$ 127 128 /** 129 * The string representation used for <code>true</code> 130 * (<code>"true"</code>). 131 */ 132 protected static final String TRUE = "true"; //$NON-NLS-1$ 133 134 /** 135 * The string representation used for <code>false</code> 136 * (<code>"false"</code>). 137 */ 138 protected static final String FALSE = "false"; //$NON-NLS-1$ 139 140 /** 141 * Singleton empty string array (optimization) 142 */ 143 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 144 145 /** 146 * The simple identifier constant (value "<code>preferences</code>") of 147 * the extension point of the Core Runtime plug-in where plug-ins declare 148 * extensions to the preference facility. A plug-in may define any number 149 * of preference extensions. 150 * 151 * @since 3.2 152 */ 153 public static final String PT_PREFERENCES = "preferences"; //$NON-NLS-1$ 154 155 /** 156 * An event object describing a change to a named property. 157 * <p> 158 * The preferences object reports property change events for internal state 159 * changes that may be of interest to external parties. A special listener 160 * interface (<code>Preferences.IPropertyChangeListener</code>) is 161 * defined for this purpose. Listeners are registered via the 162 * <code>Preferences.addPropertyChangeListener</code> method. 163 * </p> 164 * <p> 165 * Clients cannot instantiate or subclass this class. 166 * </p> 167 * 168 * @see Preferences#addPropertyChangeListener(Preferences.IPropertyChangeListener) 169 * @see Preferences.IPropertyChangeListener 170 */ 171 public static class PropertyChangeEvent extends EventObject { 172 /** 173 * All serializable objects should have a stable serialVersionUID 174 */ 175 private static final long serialVersionUID = 1L; 176 177 /** 178 * The name of the changed property. 179 */ 180 private String propertyName; 181 182 /** 183 * The old value of the changed property, or <code>null</code> if 184 * not known or not relevant. 185 */ 186 private Object oldValue; 187 188 /** 189 * The new value of the changed property, or <code>null</code> if 190 * not known or not relevant. 191 */ 192 private Object newValue; 193 194 /** 195 * Creates a new property change event. 196 * 197 * @param source the object whose property has changed 198 * @param property the property that has changed (must not be 199 * <code>null</code>) 200 * @param oldValue the old value of the property, or 201 * <code>null</code> if none 202 * @param newValue the new value of the property, or 203 * <code>null</code> if none 204 */ PropertyChangeEvent(Object source, String property, Object oldValue, Object newValue)205 protected PropertyChangeEvent(Object source, String property, Object oldValue, Object newValue) { 206 207 super(source); 208 if (property == null) { 209 throw new IllegalArgumentException(); 210 } 211 this.propertyName = property; 212 this.oldValue = oldValue; 213 this.newValue = newValue; 214 } 215 216 /** 217 * Returns the name of the property that changed. 218 * <p> 219 * Warning: there is no guarantee that the property name returned 220 * is a constant string. Callers must compare property names using 221 * <code>equals</code>, not ==. 222 *</p> 223 * 224 * @return the name of the property that changed 225 */ getProperty()226 public String getProperty() { 227 return propertyName; 228 } 229 230 /** 231 * Returns the new value of the property. 232 * 233 * @return the new value, or <code>null</code> if not known 234 * or not relevant 235 */ getNewValue()236 public Object getNewValue() { 237 return newValue; 238 } 239 240 /** 241 * Returns the old value of the property. 242 * 243 * @return the old value, or <code>null</code> if not known 244 * or not relevant 245 */ getOldValue()246 public Object getOldValue() { 247 return oldValue; 248 } 249 } 250 251 /** 252 * Listener for property changes. 253 * <p> 254 * Usage: 255 * </p> 256 * <pre> 257 * Preferences.IPropertyChangeListener listener = 258 * new Preferences.IPropertyChangeListener() { 259 * public void propertyChange(Preferences.PropertyChangeEvent event) { 260 * ... // code to deal with occurrence of property change 261 * } 262 * }; 263 * emitter.addPropertyChangeListener(listener); 264 * ... 265 * emitter.removePropertyChangeListener(listener); 266 * </pre> 267 * <p> 268 * <em>Note:</em> Depending on the means in which the property 269 * values changed, the old and new values for the property can 270 * be either typed, a string representation of the value, or <code>null</code>. 271 * Clients who wish to behave properly in all cases should all 272 * three cases in their implementation of the property change listener. 273 * </p> 274 */ 275 @FunctionalInterface 276 public interface IPropertyChangeListener extends EventListener { 277 278 /** 279 * Notification that a property has changed. 280 * <p> 281 * This method gets called when the observed object fires a property 282 * change event. 283 * </p> 284 * 285 * @param event the property change event object describing which 286 * property changed and how 287 */ propertyChange(Preferences.PropertyChangeEvent event)288 public void propertyChange(Preferences.PropertyChangeEvent event); 289 } 290 291 /** 292 * List of registered listeners (element type: 293 * <code>IPropertyChangeListener</code>). 294 * These listeners are to be informed when the current value of a property 295 * changes. 296 */ 297 protected ListenerList<IPropertyChangeListener> listeners = new ListenerList<>(); 298 299 /** 300 * The mapping from property name to 301 * property value (represented as strings). 302 */ 303 private Properties properties; 304 305 /** 306 * The mapping from property name to 307 * default property value (represented as strings); 308 * <code>null</code> if none. 309 */ 310 private Properties defaultProperties; 311 312 /** 313 * Indicates whether a value has been changed by <code>setToDefault</code> 314 * or <code>setValue</code>; initially <code>false</code>. 315 */ 316 protected boolean dirty = false; 317 318 /** 319 * Exports all non-default-valued preferences for all installed plugins to the 320 * provided file. If a file already exists at the given location, it will be deleted. 321 * If there are no preferences to export, no file will be written. 322 * <p> 323 * The file that is written can be read later using the importPreferences method. 324 * </p> 325 * @param path The absolute file system path of the file to export preferences to. 326 * @exception CoreException if this method fails. Reasons include: 327 * <ul> 328 * <li> The file could not be written.</li> 329 * </ul> 330 * @see #importPreferences(IPath) 331 * @see #validatePreferenceVersions(IPath) 332 */ exportPreferences(IPath path)333 public static void exportPreferences(IPath path) throws CoreException { 334 File file = path.toFile(); 335 if (file.exists()) 336 file.delete(); 337 file.getParentFile().mkdirs(); 338 IPreferencesService service = PreferencesService.getDefault(); 339 OutputStream output = null; 340 FileOutputStream fos = null; 341 try { 342 fos = new FileOutputStream(file); 343 output = new BufferedOutputStream(fos); 344 IEclipsePreferences node = (IEclipsePreferences) service.getRootNode().node(InstanceScope.SCOPE); 345 service.exportPreferences(node, output, (String[]) null); 346 output.flush(); 347 fos.getFD().sync(); 348 } catch (IOException e) { 349 String message = NLS.bind(PrefsMessages.preferences_errorWriting, file, e.getMessage()); 350 IStatus status = new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e); 351 throw new CoreException(status); 352 } finally { 353 if (output != null) 354 try { 355 output.close(); 356 } catch (IOException e) { 357 // ignore 358 } 359 } 360 } 361 362 /** 363 * Loads the plugin preferences from the given file, and replaces all 364 * non-default-valued preferences for all plugins with the values from this file. 365 * <p> 366 * If the file contains preferences for plug-ins that don't exist in the current 367 * install, they are ignored. This method does not validate if the plug-in 368 * versions in the preference file match the currently installed plug-ins. 369 * Clients should first call validatePreferenceVersions on the file to ensure 370 * that the versions are compatible. 371 * </p> 372 * <p> 373 * The file must have been written by the exportPreferences method. 374 * </p> 375 * @param path The absolute file system path of the file to import preferences from. 376 * @exception CoreException if this method fails. Reasons include: 377 * <ul> 378 * <li> The file does not exist.</li> 379 * <li> The file could not be read.</li> 380 * </ul> 381 * @see #exportPreferences(IPath) 382 * @see #validatePreferenceVersions(IPath) 383 */ importPreferences(IPath path)384 public static void importPreferences(IPath path) throws CoreException { 385 if (!path.toFile().exists()) { 386 String msg = NLS.bind(PrefsMessages.preferences_fileNotFound, path.toOSString()); 387 throw new CoreException(new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, 1, msg, null)); 388 } 389 IPreferencesService service = PreferencesService.getDefault(); 390 InputStream input = null; 391 try { 392 input = new BufferedInputStream(new FileInputStream(path.toFile())); 393 service.importPreferences(input); 394 } catch (FileNotFoundException e) { 395 String msg = NLS.bind(PrefsMessages.preferences_fileNotFound, path.toOSString()); 396 throw new CoreException(new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, 1, msg, e)); 397 } finally { 398 if (input != null) 399 try { 400 input.close(); 401 } catch (IOException e) { 402 // ignore 403 } 404 } 405 } 406 407 /** 408 * Validates that the preference versions in the given file match the versions 409 * of the currently installed plugins. Returns an OK status if all preferences match 410 * the currently installed plugins, otherwise a MultiStatus describing what 411 * plugins have preferences that don't match. 412 * <p> 413 * If the returned status has a <code>IStatus.WARNING</code> severity, 414 * it means that some preferences may not be applicable but for the most 415 * part they will be compatible. If the returned status has a 416 * <code>IStatus.ERROR</code> severity, it means that the preferences 417 * will probably not be compatible. 418 * <p> 419 * If the file contains preferences for plug-ins that don't exist in the current 420 * install, they are ignored. 421 * </p> 422 * <p> 423 * The file must have been written by the exportPreferences method. 424 * </p> 425 * @param file The absolute file system path of the preference file to validate. 426 * @see #exportPreferences(IPath) 427 * @see #importPreferences(IPath) 428 */ validatePreferenceVersions(IPath file)429 public static IStatus validatePreferenceVersions(IPath file) { 430 PreferencesService service = PreferencesService.getDefault(); 431 return service.validateVersions(file); 432 } 433 434 /** 435 * Creates an empty preference table. 436 * <p> 437 * Use the methods <code>load(InputStream)</code> and 438 * <code>store(InputStream)</code> to load and store these preferences. 439 * </p> 440 * @see #load(InputStream) 441 * @see #store(OutputStream, String) 442 */ Preferences()443 public Preferences() { 444 defaultProperties = new Properties(); 445 properties = new Properties(defaultProperties); 446 } 447 448 /** 449 * Adds a property change listener to this preference object. 450 * Has no effect if the identical listener is already registered. 451 * <p> 452 * <em>Note:</em> Depending on the means in which the property 453 * values changed, the old and new values for the property can 454 * be either typed, a string representation of the value, or <code>null</code>. 455 * Clients who wish to behave properly in all cases should all 456 * three cases in their implementation of the property change listener. 457 * </p> 458 * @param listener a property change listener 459 */ addPropertyChangeListener(IPropertyChangeListener listener)460 public void addPropertyChangeListener(IPropertyChangeListener listener) { 461 listeners.add(listener); 462 } 463 464 /** 465 * Removes the given listener from this preference object. 466 * Has no effect if the listener is not registered. 467 * 468 * @param listener a property change listener 469 */ removePropertyChangeListener(IPropertyChangeListener listener)470 public void removePropertyChangeListener(IPropertyChangeListener listener) { 471 listeners.remove(listener); 472 } 473 474 /** 475 * Returns whether the given property is known to this preference object, 476 * either by having an explicit setting or by having a default 477 * setting. Returns <code>false</code> if the given name is <code>null</code>. 478 * 479 * @param name the name of the property, or <code>null</code> 480 * @return <code>true</code> if either a current value or a default 481 * value is known for the named property, and <code>false</code>otherwise 482 */ contains(String name)483 public boolean contains(String name) { 484 return (properties.containsKey(name) || defaultProperties.containsKey(name)); 485 } 486 487 /** 488 * Fires a property change event corresponding to a change to the 489 * current value of the property with the given name. 490 * 491 * @param name the name of the property, to be used as the property 492 * in the event object 493 * @param oldValue the old value, or <code>null</code> if not known or not 494 * relevant 495 * @param newValue the new value, or <code>null</code> if not known or not 496 * relevant 497 */ firePropertyChangeEvent(String name, Object oldValue, Object newValue)498 protected void firePropertyChangeEvent(String name, Object oldValue, Object newValue) { 499 if (name == null) 500 throw new IllegalArgumentException(); 501 // Do we even need to fire an event? 502 if (this.listeners.size() == 0) 503 return; 504 final PropertyChangeEvent pe = new PropertyChangeEvent(this, name, oldValue, newValue); 505 for (final IPropertyChangeListener l : this.listeners) { 506 ISafeRunnable job = new ISafeRunnable() { 507 @Override 508 public void handleException(Throwable exception) { 509 // already being logged in SafeRunner#run() 510 } 511 512 @Override 513 public void run() throws Exception { 514 l.propertyChange(pe); 515 } 516 }; 517 SafeRunner.run(job); 518 } 519 } 520 521 /** 522 * Returns the current value of the boolean-valued property with the 523 * given name. 524 * Returns the default-default value (<code>false</code>) if there 525 * is no property with the given name, or if the current value 526 * cannot be treated as a boolean. 527 * The given name must not be <code>null</code>. 528 * 529 * @param name the name of the property 530 * @return the boolean-valued property 531 */ getBoolean(String name)532 public boolean getBoolean(String name) { 533 String value = properties.getProperty(name); 534 if (value == null) { 535 return BOOLEAN_DEFAULT_DEFAULT; 536 } 537 return value.equals(Preferences.TRUE); 538 } 539 540 /** 541 * Sets the current value of the boolean-valued property with the 542 * given name. The given name must not be <code>null</code>. 543 * <p> 544 * A property change event is reported if the current value of the 545 * property actually changes from its previous value. In the event 546 * object, the property name is the name of the property, and the 547 * old and new values are wrapped as objects. 548 * </p> 549 * <p> 550 * If the given value is the same as the corresponding default value 551 * for the given property, the explicit setting is deleted. 552 * Note that the recommended way of re-initializing a property to its 553 * default value is to call <code>setToDefault</code>. 554 * </p> 555 * 556 * @param name the name of the property 557 * @param value the new current value of the property 558 */ setValue(String name, boolean value)559 public void setValue(String name, boolean value) { 560 boolean defaultValue = getDefaultBoolean(name); 561 boolean oldValue = getBoolean(name); 562 if (value == defaultValue) { 563 Object removed = properties.remove(name); 564 if (removed != null) { 565 // removed an explicit setting 566 dirty = true; 567 } 568 } else { 569 properties.put(name, value ? Preferences.TRUE : Preferences.FALSE); 570 } 571 if (oldValue != value) { 572 // mark as dirty since value did really change 573 dirty = true; 574 // report property change if getValue now returns different value 575 firePropertyChangeEvent(name, oldValue ? Boolean.TRUE : Boolean.FALSE, value ? Boolean.TRUE : Boolean.FALSE); 576 } 577 } 578 579 /** 580 * Returns the default value for the boolean-valued property 581 * with the given name. 582 * Returns the default-default value (<code>false</code>) if there 583 * is no default property with the given name, or if the default 584 * value cannot be treated as a boolean. 585 * The given name must not be <code>null</code>. 586 * 587 * @param name the name of the property 588 * @return the default value of the named property 589 */ getDefaultBoolean(String name)590 public boolean getDefaultBoolean(String name) { 591 String value = defaultProperties.getProperty(name); 592 if (value == null) { 593 return BOOLEAN_DEFAULT_DEFAULT; 594 } 595 return value.equals(Preferences.TRUE); 596 } 597 598 /** 599 * Sets the default value for the boolean-valued property with the 600 * given name. The given name must not be <code>null</code>. 601 * <p> 602 * Note that the current value of the property is affected if 603 * the property's current value was its old default value, in which 604 * case it changes to the new default value. If the property's current 605 * is different from its old default value, its current value is 606 * unaffected. No property change events are reported by changing default 607 * values. 608 * </p> 609 * 610 * @param name the name of the property 611 * @param value the new default value for the property 612 */ setDefault(String name, boolean value)613 public void setDefault(String name, boolean value) { 614 defaultProperties.put(name, value ? Preferences.TRUE : Preferences.FALSE); 615 } 616 617 /** 618 * Returns the current value of the double-valued property with the 619 * given name. 620 * Returns the default-default value (<code>0.0</code>) if there 621 * is no property with the given name, or if the current value 622 * cannot be treated as a double. 623 * The given name must not be <code>null</code>. 624 * 625 * @param name the name of the property 626 * @return the double-valued property 627 */ getDouble(String name)628 public double getDouble(String name) { 629 return convertToDouble(properties.getProperty(name), DOUBLE_DEFAULT_DEFAULT); 630 } 631 632 /** 633 * Sets the current value of the double-valued property with the 634 * given name. The given name must not be <code>null</code>. 635 * <p> 636 * A property change event is reported if the current value of the 637 * property actually changes from its previous value. In the event 638 * object, the property name is the name of the property, and the 639 * old and new values are wrapped as objects. 640 * </p> 641 * <p> 642 * If the given value is the same as the corresponding default value 643 * for the given property, the explicit setting is deleted. 644 * Note that the recommended way of re-initializing a property to its 645 * default value is to call <code>setToDefault</code>. 646 * </p> 647 * 648 * @param name the name of the property 649 * @param value the new current value of the property; must be 650 * a number (not a NaN) 651 */ setValue(String name, double value)652 public void setValue(String name, double value) { 653 if (Double.isNaN(value)) { 654 throw new IllegalArgumentException(); 655 } 656 double defaultValue = getDefaultDouble(name); 657 double oldValue = getDouble(name); 658 if (value == defaultValue) { 659 Object removed = properties.remove(name); 660 if (removed != null) { 661 // removed an explicit setting 662 dirty = true; 663 } 664 } else { 665 properties.put(name, Double.toString(value)); 666 } 667 if (oldValue != value) { 668 // mark as dirty since value did really change 669 dirty = true; 670 // report property change if getValue now returns different value 671 firePropertyChangeEvent(name, Double.valueOf(oldValue), Double.valueOf(value)); 672 } 673 } 674 675 /** 676 * Returns the default value for the double-valued property 677 * with the given name. 678 * Returns the default-default value (<code>0.0</code>) if there 679 * is no default property with the given name, or if the default 680 * value cannot be treated as a double. 681 * The given name must not be <code>null</code>. 682 * 683 * @param name the name of the property 684 * @return the default value of the named property 685 */ getDefaultDouble(String name)686 public double getDefaultDouble(String name) { 687 return convertToDouble(defaultProperties.getProperty(name), DOUBLE_DEFAULT_DEFAULT); 688 } 689 690 /** 691 * Sets the default value for the double-valued property with the 692 * given name. The given name must not be <code>null</code>. 693 * <p> 694 * Note that the current value of the property is affected if 695 * the property's current value was its old default value, in which 696 * case it changes to the new default value. If the property's current 697 * is different from its old default value, its current value is 698 * unaffected. No property change events are reported by changing default 699 * values. 700 * </p> 701 * 702 * @param name the name of the property 703 * @param value the new default value for the property; must be 704 * a number (not a NaN) 705 */ setDefault(String name, double value)706 public void setDefault(String name, double value) { 707 if (Double.isNaN(value)) { 708 throw new IllegalArgumentException(); 709 } 710 defaultProperties.put(name, Double.toString(value)); 711 } 712 713 /** 714 * Converts the given raw property value string to a double. 715 * 716 * @param rawPropertyValue the raw property value, or <code>null</code> 717 * if none 718 * @param defaultValue the default value 719 * @return the raw value converted to a double, or the given 720 * <code>defaultValue</code> if the raw value is <code>null</code> or 721 * cannot be parsed as a double 722 */ convertToDouble(String rawPropertyValue, double defaultValue)723 private double convertToDouble(String rawPropertyValue, double defaultValue) { 724 double result = defaultValue; 725 if (rawPropertyValue != null) { 726 try { 727 result = Double.parseDouble(rawPropertyValue); 728 } catch (NumberFormatException e) { 729 // raw value cannot be treated as one of these 730 } 731 } 732 return result; 733 } 734 735 /** 736 * Returns the current value of the float-valued property with the 737 * given name. 738 * Returns the default-default value (<code>0.0f</code>) if there 739 * is no property with the given name, or if the current value 740 * cannot be treated as a float. 741 * The given name must not be <code>null</code>. 742 * 743 * @param name the name of the property 744 * @return the float-valued property 745 */ getFloat(String name)746 public float getFloat(String name) { 747 return convertToFloat(properties.getProperty(name), FLOAT_DEFAULT_DEFAULT); 748 } 749 750 /** 751 * Sets the current value of the float-valued property with the 752 * given name. The given name must not be <code>null</code>. 753 * <p> 754 * A property change event is reported if the current value of the 755 * property actually changes from its previous value. In the event 756 * object, the property name is the name of the property, and the 757 * old and new values are wrapped as objects. 758 * </p> 759 * <p> 760 * If the given value is the same as the corresponding default value 761 * for the given property, the explicit setting is deleted. 762 * Note that the recommended way of re-initializing a property to its 763 * default value is to call <code>setToDefault</code>. 764 * </p> 765 * 766 * @param name the name of the property 767 * @param value the new current value of the property; must be 768 * a number (not a NaN) 769 */ setValue(String name, float value)770 public void setValue(String name, float value) { 771 if (Float.isNaN(value)) { 772 throw new IllegalArgumentException(); 773 } 774 float defaultValue = getDefaultFloat(name); 775 float oldValue = getFloat(name); 776 if (value == defaultValue) { 777 Object removed = properties.remove(name); 778 if (removed != null) { 779 // removed an explicit setting 780 dirty = true; 781 } 782 } else { 783 properties.put(name, Float.toString(value)); 784 } 785 if (oldValue != value) { 786 // mark as dirty since value did really change 787 dirty = true; 788 // report property change if getValue now returns different value 789 firePropertyChangeEvent(name, Float.valueOf(oldValue), Float.valueOf(value)); 790 } 791 } 792 793 /** 794 * Returns the default value for the float-valued property 795 * with the given name. 796 * Returns the default-default value (<code>0.0f</code>) if there 797 * is no default property with the given name, or if the default 798 * value cannot be treated as a float. 799 * The given name must not be <code>null</code>. 800 * 801 * @param name the name of the property 802 * @return the default value of the named property 803 */ getDefaultFloat(String name)804 public float getDefaultFloat(String name) { 805 return convertToFloat(defaultProperties.getProperty(name), FLOAT_DEFAULT_DEFAULT); 806 } 807 808 /** 809 * Sets the default value for the float-valued property with the 810 * given name. The given name must not be <code>null</code>. 811 * <p> 812 * Note that the current value of the property is affected if 813 * the property's current value was its old default value, in which 814 * case it changes to the new default value. If the property's current 815 * is different from its old default value, its current value is 816 * unaffected. No property change events are reported by changing default 817 * values. 818 * </p> 819 * 820 * @param name the name of the property 821 * @param value the new default value for the property; must be 822 * a number (not a NaN) 823 */ setDefault(String name, float value)824 public void setDefault(String name, float value) { 825 if (Float.isNaN(value)) { 826 throw new IllegalArgumentException(); 827 } 828 defaultProperties.put(name, Float.toString(value)); 829 } 830 831 /** 832 * Converts the given raw property value string to a float. 833 * 834 * @param rawPropertyValue the raw property value, or <code>null</code> 835 * if none 836 * @param defaultValue the default value 837 * @return the raw value converted to a float, or the given 838 * <code>defaultValue</code> if the raw value is <code>null</code> or 839 * cannot be parsed as a float 840 */ convertToFloat(String rawPropertyValue, float defaultValue)841 private float convertToFloat(String rawPropertyValue, float defaultValue) { 842 float result = defaultValue; 843 if (rawPropertyValue != null) { 844 try { 845 result = Float.parseFloat(rawPropertyValue); 846 } catch (NumberFormatException e) { 847 // raw value cannot be treated as one of these 848 } 849 } 850 return result; 851 } 852 853 /** 854 * Returns the current value of the integer-valued property with the 855 * given name. 856 * Returns the default-default value (<code>0</code>) if there 857 * is no property with the given name, or if the current value 858 * cannot be treated as an integer. 859 * The given name must not be <code>null</code>. 860 * 861 * @param name the name of the property 862 * @return the int-valued property 863 */ getInt(String name)864 public int getInt(String name) { 865 return convertToInt(properties.getProperty(name), INT_DEFAULT_DEFAULT); 866 } 867 868 /** 869 * Sets the current value of the integer-valued property with the 870 * given name. The given name must not be <code>null</code>. 871 * <p> 872 * A property change event is reported if the current value of the 873 * property actually changes from its previous value. In the event 874 * object, the property name is the name of the property, and the 875 * old and new values are wrapped as objects. 876 * </p> 877 * <p> 878 * If the given value is the same as the corresponding default value 879 * for the given property, the explicit setting is deleted. 880 * Note that the recommended way of re-initializing a property to its 881 * default value is to call <code>setToDefault</code>. 882 * </p> 883 * 884 * @param name the name of the property 885 * @param value the new current value of the property 886 */ setValue(String name, int value)887 public void setValue(String name, int value) { 888 int defaultValue = getDefaultInt(name); 889 int oldValue = getInt(name); 890 if (value == defaultValue) { 891 Object removed = properties.remove(name); 892 if (removed != null) { 893 // removed an explicit setting 894 dirty = true; 895 } 896 } else { 897 properties.put(name, Integer.toString(value)); 898 } 899 if (oldValue != value) { 900 // mark as dirty since value did really change 901 dirty = true; 902 // report property change if getValue now returns different value 903 firePropertyChangeEvent(name, Integer.valueOf(oldValue), Integer.valueOf(value)); 904 } 905 } 906 907 /** 908 * Returns the default value for the integer-valued property 909 * with the given name. 910 * Returns the default-default value (<code>0</code>) if there 911 * is no default property with the given name, or if the default 912 * value cannot be treated as an integer. 913 * The given name must not be <code>null</code>. 914 * 915 * @param name the name of the property 916 * @return the default value of the named property 917 */ getDefaultInt(String name)918 public int getDefaultInt(String name) { 919 return convertToInt(defaultProperties.getProperty(name), INT_DEFAULT_DEFAULT); 920 } 921 922 /** 923 * Sets the default value for the integer-valued property with the 924 * given name. The given name must not be <code>null</code>. 925 * <p> 926 * Note that the current value of the property is affected if 927 * the property's current value was its old default value, in which 928 * case it changes to the new default value. If the property's current 929 * is different from its old default value, its current value is 930 * unaffected. No property change events are reported by changing default 931 * values. 932 * </p> 933 * 934 * @param name the name of the property 935 * @param value the new default value for the property 936 */ setDefault(String name, int value)937 public void setDefault(String name, int value) { 938 defaultProperties.put(name, Integer.toString(value)); 939 } 940 941 /** 942 * Converts the given raw property value string to an int. 943 * 944 * @param rawPropertyValue the raw property value, or <code>null</code> 945 * if none 946 * @param defaultValue the default value 947 * @return the raw value converted to an int, or the given 948 * <code>defaultValue</code> if the raw value is <code>null</code> or 949 * cannot be parsed as an int 950 */ convertToInt(String rawPropertyValue, int defaultValue)951 private int convertToInt(String rawPropertyValue, int defaultValue) { 952 int result = defaultValue; 953 if (rawPropertyValue != null) { 954 try { 955 result = Integer.parseInt(rawPropertyValue); 956 } catch (NumberFormatException e) { 957 // raw value cannot be treated as one of these 958 } 959 } 960 return result; 961 } 962 963 /** 964 * Returns the current value of the long-valued property with the 965 * given name. 966 * Returns the default-default value (<code>0L</code>) if there 967 * is no property with the given name, or if the current value 968 * cannot be treated as a long. 969 * The given name must not be <code>null</code>. 970 * 971 * @param name the name of the property 972 * @return the long-valued property 973 */ getLong(String name)974 public long getLong(String name) { 975 return convertToLong(properties.getProperty(name), LONG_DEFAULT_DEFAULT); 976 } 977 978 /** 979 * Sets the current value of the long-valued property with the 980 * given name. The given name must not be <code>null</code>. 981 * <p> 982 * A property change event is reported if the current value of the 983 * property actually changes from its previous value. In the event 984 * object, the property name is the name of the property, and the 985 * old and new values are wrapped as objects. 986 * </p> 987 * <p> 988 * If the given value is the same as the corresponding default value 989 * for the given property, the explicit setting is deleted. 990 * Note that the recommended way of re-initializing a property to its 991 * default value is to call <code>setToDefault</code>. 992 * </p> 993 * 994 * @param name the name of the property 995 * @param value the new current value of the property 996 */ setValue(String name, long value)997 public void setValue(String name, long value) { 998 long defaultValue = getDefaultLong(name); 999 long oldValue = getLong(name); 1000 if (value == defaultValue) { 1001 Object removed = properties.remove(name); 1002 if (removed != null) { 1003 // removed an explicit setting 1004 dirty = true; 1005 } 1006 } else { 1007 properties.put(name, Long.toString(value)); 1008 } 1009 if (oldValue != value) { 1010 // mark as dirty since value did really change 1011 dirty = true; 1012 // report property change if getValue now returns different value 1013 firePropertyChangeEvent(name, Long.valueOf(oldValue), Long.valueOf(value)); 1014 } 1015 } 1016 1017 /** 1018 * Returns the default value for the long-valued property 1019 * with the given name. 1020 * Returns the default-default value (<code>0L</code>) if there 1021 * is no default property with the given name, or if the default 1022 * value cannot be treated as a long. 1023 * The given name must not be <code>null</code>. 1024 * 1025 * @param name the name of the property 1026 * @return the default value of the named property 1027 */ getDefaultLong(String name)1028 public long getDefaultLong(String name) { 1029 return convertToLong(defaultProperties.getProperty(name), LONG_DEFAULT_DEFAULT); 1030 } 1031 1032 /** 1033 * Sets the default value for the long-valued property with the 1034 * given name. The given name must not be <code>null</code>. 1035 * <p> 1036 * Note that the current value of the property is affected if 1037 * the property's current value was its old default value, in which 1038 * case it changes to the new default value. If the property's current 1039 * is different from its old default value, its current value is 1040 * unaffected. No property change events are reported by changing default 1041 * values. 1042 * </p> 1043 * 1044 * @param name the name of the property 1045 * @param value the new default value for the property 1046 */ setDefault(String name, long value)1047 public void setDefault(String name, long value) { 1048 defaultProperties.put(name, Long.toString(value)); 1049 } 1050 1051 /** 1052 * Converts the given raw property value string to a long. 1053 * 1054 * @param rawPropertyValue the raw property value, or <code>null</code> 1055 * if none 1056 * @param defaultValue the default value 1057 * @return the raw value converted to a long, or the given 1058 * <code>defaultValue</code> if the raw value is <code>null</code> or 1059 * cannot be parsed as a long 1060 */ convertToLong(String rawPropertyValue, long defaultValue)1061 private long convertToLong(String rawPropertyValue, long defaultValue) { 1062 long result = defaultValue; 1063 if (rawPropertyValue != null) { 1064 try { 1065 result = Long.parseLong(rawPropertyValue); 1066 } catch (NumberFormatException e) { 1067 // raw value cannot be treated as one of these 1068 } 1069 } 1070 return result; 1071 } 1072 1073 /** 1074 * Returns the current value of the string-valued property with the 1075 * given name. 1076 * Returns the default-default value (the empty string <code>""</code>) 1077 * if there is no property with the given name. 1078 * The given name must not be <code>null</code>. 1079 * 1080 * @param name the name of the property 1081 * @return the string-valued property 1082 */ getString(String name)1083 public String getString(String name) { 1084 String value = properties.getProperty(name); 1085 return (value != null ? value : STRING_DEFAULT_DEFAULT); 1086 } 1087 1088 /** 1089 * Sets the current value of the string-valued property with the 1090 * given name. The given name must not be <code>null</code>. 1091 * <p> 1092 * A property change event is reported if the current value of the 1093 * property actually changes from its previous value. In the event 1094 * object, the property name is the name of the property, and the 1095 * old and new values are wrapped as objects. 1096 * </p> 1097 * <p> 1098 * If the given value is the same as the corresponding default value 1099 * for the given property, the explicit setting is deleted. 1100 * Note that the recommended way of re-initializing a property to its 1101 * default value is to call <code>setToDefault</code>. 1102 * </p> 1103 * 1104 * @param name the name of the property 1105 * @param value the new current value of the property 1106 */ setValue(String name, String value)1107 public void setValue(String name, String value) { 1108 if (value == null) { 1109 throw new IllegalArgumentException(); 1110 } 1111 String defaultValue = getDefaultString(name); 1112 String oldValue = getString(name); 1113 if (value.equals(defaultValue)) { 1114 Object removed = properties.remove(name); 1115 if (removed != null) { 1116 // removed an explicit setting 1117 dirty = true; 1118 } 1119 } else { 1120 properties.put(name, value); 1121 } 1122 if (!oldValue.equals(value)) { 1123 // mark as dirty since value did really change 1124 dirty = true; 1125 // report property change if getValue now returns different value 1126 firePropertyChangeEvent(name, oldValue, value); 1127 } 1128 } 1129 1130 /** 1131 * Returns the default value for the string-valued property 1132 * with the given name. 1133 * Returns the default-default value (the empty string <code>""</code>) 1134 * is no default property with the given name, or if the default 1135 * value cannot be treated as a string. 1136 * The given name must not be <code>null</code>. 1137 * 1138 * @param name the name of the property 1139 * @return the default value of the named property 1140 */ getDefaultString(String name)1141 public String getDefaultString(String name) { 1142 String value = defaultProperties.getProperty(name); 1143 return (value != null ? value : STRING_DEFAULT_DEFAULT); 1144 } 1145 1146 /** 1147 * Sets the default value for the string-valued property with the 1148 * given name. The given name must not be <code>null</code>. 1149 * <p> 1150 * Note that the current value of the property is affected if 1151 * the property's current value was its old default value, in which 1152 * case it changes to the new default value. If the property's current 1153 * is different from its old default value, its current value is 1154 * unaffected. No property change events are reported by changing default 1155 * values. 1156 * </p> 1157 * 1158 * @param name the name of the property 1159 * @param value the new default value for the property 1160 */ setDefault(String name, String value)1161 public void setDefault(String name, String value) { 1162 if (value == null) { 1163 throw new IllegalArgumentException(); 1164 } 1165 defaultProperties.put(name, value); 1166 } 1167 1168 /** 1169 * Returns whether the property with the given name has the default value in 1170 * virtue of having no explicitly set value. 1171 * Returns <code>false</code> if the given name is <code>null</code>. 1172 * 1173 * @param name the name of the property, or <code>null</code> 1174 * @return <code>true</code> if the property has no explicitly set value, 1175 * and <code>false</code> otherwise (including the case where the property 1176 * is unknown to this object) 1177 */ isDefault(String name)1178 public boolean isDefault(String name) { 1179 return !properties.containsKey(name); 1180 } 1181 1182 /** 1183 * Sets the current value of the property with the given name back 1184 * to its default value. Has no effect if the property does not have 1185 * its own current value. The given name must not be <code>null</code>. 1186 * <p> 1187 * Note that the recommended way of re-initializing a property to the 1188 * appropriate default value is to call <code>setToDefault</code>. 1189 * This is implemented by removing the named value from the object, 1190 * thereby exposing the default value. 1191 * </p> 1192 * <p> 1193 * A property change event is always reported. In the event 1194 * object, the property name is the name of the property, and the 1195 * old and new values are either strings, or <code>null</code> 1196 * indicating the default-default value. 1197 * </p> 1198 * 1199 * @param name the name of the property 1200 */ setToDefault(String name)1201 public void setToDefault(String name) { 1202 Object oldPropertyValue = properties.remove(name); 1203 if (oldPropertyValue != null) { 1204 dirty = true; 1205 } 1206 String newValue = defaultProperties.getProperty(name, null); 1207 // n.b. newValue == null if there is no default value 1208 // can't determine correct default-default without knowing type 1209 firePropertyChangeEvent(name, oldPropertyValue, newValue); 1210 } 1211 1212 /** 1213 * Returns a list of all properties known to this preference object which 1214 * have current values other than their default value. 1215 * 1216 * @return an array of property names 1217 */ propertyNames()1218 public String[] propertyNames() { 1219 return properties.keySet().toArray(EMPTY_STRING_ARRAY); 1220 } 1221 1222 /** 1223 * Returns a list of all properties known to this preference object which 1224 * have an explicit default value set. 1225 * 1226 * @return an array of property names 1227 */ defaultPropertyNames()1228 public String[] defaultPropertyNames() { 1229 return defaultProperties.keySet().toArray(EMPTY_STRING_ARRAY); 1230 } 1231 1232 /** 1233 * Returns whether the current values in this preference object 1234 * require saving. 1235 * 1236 * @return <code>true</code> if at least one of the properties 1237 * known to this preference object has a current value different from its 1238 * default value, and <code>false</code> otherwise 1239 */ needsSaving()1240 public boolean needsSaving() { 1241 return dirty; 1242 } 1243 1244 /** 1245 * Saves the non-default-valued properties known to this preference object to 1246 * the given output stream using 1247 * <code>Properties.store(OutputStream,String)</code>. 1248 * <p> 1249 * Note that the output is unconditionally written, even when 1250 * <code>needsSaving</code> is <code>false</code>. 1251 * </p> 1252 * 1253 * @param out the output stream 1254 * @param header a comment to be included in the output, or 1255 * <code>null</code> if none 1256 * @exception IOException if there is a problem saving this preference object 1257 * @see Properties#store(OutputStream,String) 1258 */ store(OutputStream out, String header)1259 public void store(OutputStream out, String header) throws IOException { 1260 properties.store(out, header); 1261 dirty = false; 1262 } 1263 1264 /** 1265 * Loads the non-default-valued properties for this preference object from the 1266 * given input stream using 1267 * <code>java.util.Properties.load(InputStream)</code>. Default property 1268 * values are not affected. 1269 * 1270 * @param in the input stream 1271 * @exception IOException if there is a problem loading this preference 1272 * object 1273 * @see java.util.Properties#load(InputStream) 1274 */ load(InputStream in)1275 public void load(InputStream in) throws IOException { 1276 properties.load(in); 1277 dirty = false; 1278 } 1279 } 1280