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