View Javadoc

1   /*
2    * $HeadURL: $
3    * $Date: $
4    * $Revision: $
5    * $Author: $
6    * 
7    * Copyright (c) 2005 MindTree Consulting Ltd. 
8    * 
9    * This file is part of Insight.
10   * 
11   * Insight is free software: you can redistribute it and/or modify it under the 
12   * terms of the GNU General Public License as published by the Free Software 
13   * Foundation, either version 3 of the License, or (at your option) any later 
14   * version.
15   * 
16   * Insight is distributed in the hope that it will be useful, but 
17   * WITHOUT ANY WARRANTY; without even the implied warranty of 
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General 
19   * Public License for more details.
20   * 
21   * You should have received a copy of the GNU General Public License along with 
22   * Insight.  If not, see <http://www.gnu.org/licenses/>.
23   */
24  
25  package com.mindtree.techworks.insight.preferences;
26  
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.Map;
34  
35  import com.mindtree.techworks.insight.preferences.model.Preference;
36  import com.mindtree.techworks.insight.preferences.model.PreferenceInfo;
37  import com.mindtree.techworks.insight.preferences.model.PreferenceModelManager;
38  
39  
40  /**
41   * <p>
42   * This is a singleton class to be used by the entire application for accessing
43   * and storing <code>Preferences</code>.
44   * </p>
45   * <p>
46   * The PreferencesManager is designed not to throw any exceptions, and whenever
47   * a particular exception value is missing or cannot be saved, it will simply
48   * return <code>null</code>. It is upto the calling method to determine
49   * whether it wants to terminate the usecase or continue with the execution of
50   * the program or display a warning to the user.
51   * </p>
52   * 
53   * @see com.mindtree.techworks.insight.preferences.model.Preference Preference
54   * @author Bindul Bhowmik
55   * @version $Revision: 27 $ $Date: 2007-12-16 04:58:03 -0700 (Sun, 16 Dec 2007) $
56   */
57  public class PreferenceManager implements PreferenceModelManager {
58  
59  	/**
60  	 * Instance for the singleton.
61  	 */
62  	private static PreferenceManager instance = null;
63  
64  	/**
65  	 * Seperator between the preference and the preference-attribute ids for a
66  	 * listener.
67  	 */
68  	private static final String LISTENER_KEY_SEPERATOR = "_";
69  
70  	/**
71  	 * Interval (in milliseconds) after which dirty preferences are checked to
72  	 * be stored.
73  	 */
74  	private static final int MAX_DIRTY_PREFERENCES = 5;
75  
76  	/**
77  	 * Local cache for preferences
78  	 */
79  	private HashMap preferenceCache = null;
80  
81  	/**
82  	 * Holds all the listeners interested in being notified of a preference
83  	 * attribute value change.
84  	 */
85  	private HashMap attributeChangeListeners = null;
86  
87  	/**
88  	 * Holds all the listeners interested in being notified of a preference
89  	 * change
90  	 */
91  	private HashMap preferenceChangeListeners = null;
92  
93  	/**
94  	 * If a preference notification lock is enabled, all
95  	 */
96  	private LinkedList preferenceChangesToNotify = new LinkedList();
97  
98  	/**
99  	 * Should Preference Notifications be locked.
100 	 */
101 	private boolean lockPreferenceNotifications = false;
102 
103 	/**
104 	 * Stores the ids of all dirty preferences.
105 	 */
106 	private List dirtyPreferences = null;
107 
108 	/**
109 	 * Keeps a track of total number of dirty preferences.
110 	 */
111 	private int totalDirtyCount;
112 
113 	/**
114 	 * Caches the preferenceNameList
115 	 */
116 	private Map preferenceNameMap = null;
117 
118 	/**
119 	 * Checks if all preferences are loaded.
120 	 */
121 	private boolean isAllPreferencesLoaded = false;
122 
123 	/**
124 	 * Private constructor to avoid instantiations outside the class.
125 	 */
126 	private PreferenceManager () {
127 
128 		preferenceCache = new HashMap();
129 		attributeChangeListeners = new HashMap();
130 		preferenceChangeListeners = new HashMap();
131 		dirtyPreferences = new ArrayList();
132 	}
133 
134 	/**
135 	 * Returns the singleton instance of PreferenceManager.
136 	 * 
137 	 * @return The singleton instance of PreferenceManager
138 	 */
139 	public static PreferenceManager getInstance () {
140 
141 		if (null == instance) {
142 			//Instantiate the instance - double check lock!
143 			synchronized (PreferenceManager.class) {
144 				if (null == instance) {
145 					instance = new PreferenceManager();
146 				}
147 			}
148 		}
149 		return instance;
150 	}
151 
152 	/**
153 	 * Checks if any preferences are dirty, then stores them to the store.
154 	 */
155 	private void storePreferences () {
156 
157 		//do some writing
158 		synchronized (dirtyPreferences) {
159 			List preferencesToStore = new ArrayList();
160 			for (Iterator dirtyPreferencesItr = dirtyPreferences.iterator(); dirtyPreferencesItr
161 					.hasNext();) {
162 				String completeDirtyPreferenceId = (String) dirtyPreferencesItr
163 						.next();
164 				String dirtyPreferenceId = completeDirtyPreferenceId;
165 				int idIndex = completeDirtyPreferenceId
166 						.indexOf(Preference.ID_SEPERATOR);
167 				if (idIndex > -1) {
168 					dirtyPreferenceId = completeDirtyPreferenceId.substring(0,
169 							idIndex);
170 				}
171 				preferencesToStore.add(preferenceCache.get(dirtyPreferenceId));
172 			}
173 			PreferenceDataHandler dataHandler = null;
174 			try {
175 				dataHandler = PreferenceDataHandlerFactory.getDefaultHandler();
176 				dataHandler.savePreferences(preferencesToStore);
177 			} catch (PreferenceHandlerInstantiationException e) {
178 				e.printStackTrace();
179 			} catch (PreferenceHandlerStoreException e) {
180 				// TODO Decide how to notify the application
181 				e.printStackTrace();
182 			}
183 			totalDirtyCount = 0;
184 		}
185 	}
186 
187 	/**
188 	 * Returns a Preference
189 	 * 
190 	 * @param preferenceId The preferenceId for which the preference is sought
191 	 * @return Preference The Preference
192 	 */
193 	public Preference getPreference (String preferenceId) {
194 
195 		Preference preferenceToReturn = null;
196 
197 		if (!preferenceCache.containsKey(preferenceId)) {
198 			loadPreference(preferenceId);
199 		}
200 		preferenceToReturn = (Preference) preferenceCache.get(preferenceId);
201 
202 		int parentLocation = preferenceId.indexOf(Preference.ID_SEPERATOR);
203 
204 		if (null == preferenceToReturn && parentLocation > 0) {
205 			String parentPreferenceId = preferenceId.substring(0,
206 					parentLocation);
207 			if (!preferenceCache.containsKey(parentPreferenceId)) {
208 				loadPreference(parentPreferenceId);
209 			}
210 			Preference parentPreference = (Preference) preferenceCache
211 					.get(parentPreferenceId);
212 			preferenceToReturn = parentPreference
213 					.getPreferenceById(preferenceId
214 							.substring(parentLocation + 1));
215 		}
216 
217 		return preferenceToReturn;
218 	}
219 
220 	/**
221 	 * Loads a preference into the cache
222 	 * 
223 	 * @param preferenceId
224 	 */
225 	private void loadPreference (String preferenceId) {
226 
227 		PreferenceDataHandler dataHandler = null;
228 		Preference preference = null;
229 		try {
230 			dataHandler = PreferenceDataHandlerFactory.getDefaultHandler();
231 			preference = dataHandler.getPreference(preferenceId);
232 		} catch (PreferenceHandlerInstantiationException e) {
233 			e.printStackTrace();
234 		}
235 		if (null != preference) {
236 			preference.registerPreferenceModelManager(this);
237 			preferenceCache.put(preferenceId, preference);
238 		}
239 	}
240 
241 	/**
242 	 * Returns preference info
243 	 * 
244 	 * @return List of
245 	 *         {@link com.mindtree.techworks.insight.preferences.model.PreferenceInfo PreferenceInfo}
246 	 *         objects.
247 	 */
248 	public Collection getAllPreferenceNames () {
249 
250 		Collection preferenceNameList = null;
251 
252 		if (null == preferenceNameMap) {
253 			PreferenceDataHandler dataHandler = null;
254 			try {
255 				dataHandler = PreferenceDataHandlerFactory.getDefaultHandler();
256 				preferenceNameList = dataHandler.getPreferenceNameList();
257 				// Populate the Preference Name map
258 				preferenceNameMap = new HashMap();
259 				for (Iterator preferenceNameItr = preferenceNameList.iterator(); preferenceNameItr
260 						.hasNext();) {
261 					PreferenceInfo preferenceInfo = (PreferenceInfo) preferenceNameItr
262 							.next();
263 					preferenceNameMap.put(preferenceInfo.getId(),
264 							preferenceInfo);
265 				}
266 			} catch (PreferenceHandlerInstantiationException e) {
267 				e.printStackTrace();
268 			}
269 		} else {
270 			preferenceNameList = preferenceNameMap.values();
271 		}
272 
273 		return preferenceNameList;
274 	}
275 
276 	/**
277 	 * Returns all the preferences
278 	 * 
279 	 * @return A {@link Collection Collection}of Preferences
280 	 */
281 	public Collection getAllPreferences () {
282 
283 		if (!isAllPreferencesLoaded) {
284 
285 			//Preferences are not loaded, load them.
286 			if (null == preferenceNameMap) {
287 				//Load the preference names
288 				getAllPreferenceNames();
289 			}
290 
291 			//Load the preferences
292 			PreferenceDataHandler dataHandler = null;
293 			try {
294 				dataHandler = PreferenceDataHandlerFactory.getDefaultHandler();
295 				Collection allPreferences = dataHandler.getAllPreferences();
296 				for (Iterator allPreferencesIterator = allPreferences
297 						.iterator(); allPreferencesIterator.hasNext();) {
298 					Preference preference = (Preference) allPreferencesIterator
299 							.next();
300 					if (!preferenceCache.containsKey(preference.getId())) {
301 						preference.registerPreferenceModelManager(this);
302 						preferenceCache.put(preference.getId(), preference);
303 					}
304 				}
305 
306 			} catch (PreferenceHandlerInstantiationException e) {
307 				e.printStackTrace();
308 			}
309 
310 			isAllPreferencesLoaded = true;
311 		}
312 		return preferenceCache.values();
313 	}
314 
315 	/**
316 	 * Registeres a listener to be notified of changes in attribute values.
317 	 * 
318 	 * @param completePreferenceId The preference the listener is interested in
319 	 * @param attributeId The attribute in the preference the listener is
320 	 *            interested in
321 	 * @param listener The listener to register. Must implement
322 	 *            {@link PreferenceAttributeChangeListener PreferenceAttributeChangeListener}
323 	 */
324 	public void registerListener (String completePreferenceId,
325 			String attributeId, PreferenceAttributeChangeListener listener) {
326 
327 		String key = completePreferenceId + LISTENER_KEY_SEPERATOR
328 				+ attributeId;
329 		ArrayList listenerList = null;
330 		if (attributeChangeListeners.containsKey(key)) {
331 			listenerList = (ArrayList) attributeChangeListeners.get(key);
332 		} else {
333 			listenerList = new ArrayList();
334 		}
335 
336 		if (!listenerList.contains(listener)) {
337 			listenerList.add(listener);
338 			attributeChangeListeners.put(key, listenerList);
339 		}
340 	}
341 
342 	/**
343 	 * Removes a listener from the list of listeners for an attribute, if
344 	 * already registered.
345 	 * 
346 	 * @param completePreferenceId The preference the listener is interested in
347 	 * @param attributeId The attribute in the preference the listener is
348 	 *            interested in
349 	 * @param listener The listener to register.
350 	 */
351 	public void deregisterListener (String completePreferenceId,
352 			String attributeId, PreferenceAttributeChangeListener listener) {
353 
354 		String key = completePreferenceId + LISTENER_KEY_SEPERATOR
355 				+ attributeId;
356 		if (attributeChangeListeners.containsKey(key)) {
357 			ArrayList listenerList = (ArrayList) attributeChangeListeners
358 					.get(key);
359 			listenerList.remove(listener);
360 		}
361 	}
362 
363 	/**
364 	 * Registeres a listener to be notified of changes in preference values.
365 	 * 
366 	 * @param completePreferenceId The preference the listener is interested in
367 	 * @param listener The listener to register. Must implement
368 	 *            {@link PreferenceChangeListener PreferenceChangeListener}
369 	 */
370 	public void registerListener (String completePreferenceId,
371 			PreferenceChangeListener listener) {
372 
373 		ArrayList listenerList = null;
374 		if (preferenceChangeListeners.containsKey(completePreferenceId)) {
375 			listenerList = (ArrayList) preferenceChangeListeners
376 					.get(completePreferenceId);
377 		} else {
378 			listenerList = new ArrayList();
379 		}
380 
381 		if (!listenerList.contains(listener)) {
382 			listenerList.add(listener);
383 			preferenceChangeListeners.put(completePreferenceId, listenerList);
384 		}
385 
386 	}
387 
388 	/**
389 	 * Removes a listener from the list of listeners for a preference, if
390 	 * already registered.
391 	 * 
392 	 * @param completePreferenceId The preference the listener is interested in
393 	 * @param listener The listener to register.
394 	 */
395 	public void deregisterListener (String completePreferenceId,
396 			PreferenceChangeListener listener) {
397 
398 		if (preferenceChangeListeners.containsKey(completePreferenceId)) {
399 			ArrayList listenerList = (ArrayList) preferenceChangeListeners
400 					.get(completePreferenceId);
401 			listenerList.remove(listener);
402 		}
403 	}
404 
405 	/**
406 	 * Notifies all interested parties about change in an Attribute's value.
407 	 * Also helps in lazy writing.
408 	 * <p>
409 	 * This method is called by the <code>PreferenceAttribute</code> whose
410 	 * value changes. It is used by the PreferenceManager to notify all
411 	 * interested instances of
412 	 * {@link PreferenceAttributeChangeListener PreferenceAttributeChangeListener}
413 	 * instances registered with the PreferenceManager.
414 	 * </p>
415 	 * <p>
416 	 * This method is also used by the manager to lazy write the preferences. If
417 	 * a different instance implements
418 	 * {@link PreferenceModelManager PreferenceModelManager}is used, then the
419 	 * instance of PreferenceManager should register with that instance for
420 	 * notifications of all attribute value changes.
421 	 * </p>
422 	 * 
423 	 * @see com.mindtree.techworks.insight.preferences.model.PreferenceModelManager#attributeValueChanged(java.lang.String,
424 	 *      java.lang.String, java.lang.String)
425 	 */
426 	public void attributeValueChanged (String completePreferenceId,
427 			String attributeId, String newValue) {
428 
429 		// Attribute Change Notification
430 		String key = completePreferenceId + LISTENER_KEY_SEPERATOR
431 				+ attributeId;
432 		if (attributeChangeListeners.containsKey(key)) {
433 			ArrayList listenerList = (ArrayList) attributeChangeListeners
434 					.get(key);
435 			for (Iterator listenerIterator = listenerList.iterator(); listenerIterator
436 					.hasNext();) {
437 				PreferenceAttributeChangeListener listener = (PreferenceAttributeChangeListener) listenerIterator
438 						.next();
439 				listener.attributeValueChanged(completePreferenceId,
440 						attributeId, newValue);
441 			}
442 		}
443 		
444 		// Preference change notification
445 		notifyPreferenceChangeListeners(completePreferenceId);
446 
447 		// Lazy Writing.
448 		if (!dirtyPreferences.contains(completePreferenceId)) {
449 			dirtyPreferences.add(completePreferenceId);
450 		}
451 		totalDirtyCount++ ;
452 		// Store the preferences based on Dirty Count
453 		if (totalDirtyCount >= MAX_DIRTY_PREFERENCES) {
454 			storePreferences();
455 		}
456 	}
457 
458 	/**
459 	 * This method is used by the Manager to primarily regenerate the
460 	 * PreferenceInfo for the preference.
461 	 * 
462 	 * @see com.mindtree.techworks.insight.preferences.model.PreferenceModelManager#childPreferenceChanged(java.lang.String,
463 	 *      java.lang.String, int)
464 	 */
465 	public void childPreferenceChanged (String preferenceId,
466 			String childPreferenceId, int operation) {
467 
468 		// Preference change notification
469 		notifyPreferenceChangeListeners(preferenceId);
470 		
471 		String parentPreferenceId = preferenceId;
472 		if (preferenceId.indexOf(Preference.ID_SEPERATOR) > -1) {
473 			parentPreferenceId = preferenceId.substring(0, preferenceId
474 					.indexOf(Preference.ID_SEPERATOR));
475 		}
476 
477 		if (null != preferenceNameMap
478 				&& preferenceCache.containsKey(parentPreferenceId)) {
479 			// Regenerate the preference info
480 			Preference preference = (Preference) preferenceCache
481 					.get(parentPreferenceId);
482 			preferenceNameMap.put(parentPreferenceId, preference
483 					.getPreferenceInfo());
484 		}
485 
486 		// Lazy Writing.
487 		if (!dirtyPreferences.contains(preferenceId)) {
488 			dirtyPreferences.add(preferenceId);
489 		}
490 		totalDirtyCount++ ;
491 		// Store the preferences based on Dirty Count
492 		if (totalDirtyCount >= MAX_DIRTY_PREFERENCES) {
493 			storePreferences();
494 		}
495 
496 	}
497 
498 	/**
499 	 * Method to be called when the application is being shut down. The manager
500 	 * will store all dirty preferences.
501 	 */
502 	public void applicationShuttingDown () {
503 
504 		if (totalDirtyCount > 0) {
505 			storePreferences();
506 		}
507 	}
508 
509 	/**
510 	 * @see com.mindtree.techworks.insight.preferences.model.PreferenceModelManager#preferenceAttributeChanged(java.lang.String,
511 	 *      java.lang.String, int)
512 	 */
513 	public void preferenceAttributeChanged (String preferenceId,
514 			String preferenceAttributeId, int operation) {
515 
516 		// Preference change notification
517 		notifyPreferenceChangeListeners(preferenceId);
518 		
519 		// Lazy Writing.
520 		if (!dirtyPreferences.contains(preferenceId)) {
521 			dirtyPreferences.add(preferenceId);
522 		}
523 		totalDirtyCount++ ;
524 		// Store the preferences based on Dirty Count
525 		if (totalDirtyCount >= MAX_DIRTY_PREFERENCES) {
526 			storePreferences();
527 		}
528 	}
529 
530 	/**
531 	 * Informs this PreferenceManager of an impending preference(s) save
532 	 */
533 	public void startPreferencesSave () {
534 
535 		lockPreferenceNotifications = true;
536 	}
537 
538 	/**
539 	 * Informs this PreferenceManager that the save is complete
540 	 */
541 	public void endPreferencesSave () {
542 		lockPreferenceNotifications = false;
543 		if (!preferenceChangesToNotify.isEmpty()) {
544 			for (int i = preferenceChangesToNotify.size(); i >0; i--) {
545 				notifyPreferenceChangeListeners((String) preferenceChangesToNotify.removeFirst());
546 			}
547 		}
548 	}
549 	
550 	/**
551 	 * Notifies a listener on change of a preference
552 	 * @param completePreferenceId The preference id to notify for
553 	 */
554 	private void notifyPreferenceChangeListeners(String completePreferenceId) {
555 		if (lockPreferenceNotifications) {
556 			if (!preferenceChangesToNotify.contains(completePreferenceId)) {
557 				preferenceChangesToNotify.addLast(completePreferenceId);
558 			}
559 		} else {
560 			Iterator keys = preferenceChangeListeners.keySet().iterator();
561 			while (keys.hasNext()) {
562 				String key = (String) keys.next();
563 				if (completePreferenceId.startsWith(key)) {
564 					Iterator listeners = ((List) preferenceChangeListeners.get(key)).iterator();
565 					while (listeners.hasNext()) {
566 						((PreferenceChangeListener) listeners.next()).preferenceChanged(key);
567 					}
568 				}
569 			}
570 		}
571 	}
572 
573 }