View Javadoc
1   /*
2    * Metis: Java Data Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.metis.preference;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
21  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
22  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
23  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar.OceanusEventProvider;
24  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
25  import io.github.tonywasher.joceanus.oceanus.resource.OceanusBundleId;
26  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
27  import io.github.tonywasher.joceanus.metis.exc.MetisDataException;
28  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
29  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
30  import io.github.tonywasher.joceanus.metis.viewer.MetisViewerEntry;
31  import io.github.tonywasher.joceanus.metis.viewer.MetisViewerManager;
32  import io.github.tonywasher.joceanus.metis.viewer.MetisViewerStandardEntry;
33  
34  import java.util.Arrays;
35  import java.util.Collection;
36  import java.util.LinkedHashMap;
37  import java.util.Map;
38  import java.util.function.Predicate;
39  import java.util.prefs.BackingStoreException;
40  import java.util.prefs.Preferences;
41  
42  /**
43   * Wrapper class for java preferences.
44   *
45   * @author Tony Washer
46   */
47  public abstract class MetisPreferenceSet
48          implements MetisFieldItem, OceanusEventProvider<MetisPreferenceEvent> {
49      /**
50       * Id interface.
51       */
52      public interface MetisPreferenceId {
53      }
54  
55      /**
56       * Unknown preference string.
57       */
58      protected static final String ERROR_UNKNOWN = "Unknown Preference: ";
59  
60      /**
61       * Invalid preference string.
62       */
63      protected static final String ERROR_INVALID = "Invalid Preference: ";
64  
65      /**
66       * The Preference Manager.
67       */
68      private final MetisPreferenceManager thePreferenceManager;
69  
70      /**
71       * The Event Manager.
72       */
73      private final OceanusEventManager<MetisPreferenceEvent> theEventManager;
74  
75      /**
76       * Report fields.
77       */
78      private final MetisFieldSet<MetisPreferenceSet> theFields;
79  
80      /**
81       * The Preference node for this set.
82       */
83      private final Preferences theHandle;
84  
85      /**
86       * The map of preferences.
87       */
88      private final Map<String, MetisPreferenceItem> theNameMap;
89  
90      /**
91       * The map of preferences.
92       */
93      private final Map<MetisPreferenceKey, MetisPreferenceItem> theKeyMap;
94  
95      /**
96       * The list of preferences that have a value on initialisation.
97       */
98      private final String[] theActive;
99  
100     /**
101      * The name of the preferenceSet.
102      */
103     private final String theName;
104 
105     /**
106      * The viewer entry.
107      */
108     private final MetisViewerEntry theViewerEntry;
109 
110     /**
111      * Is this a hidden preferenceSet.
112      */
113     private boolean isHidden;
114 
115     /**
116      * Constructor.
117      *
118      * @param pManager the preference manager
119      * @param pId      the resource id for the set name
120      * @throws OceanusException on error
121      */
122     protected MetisPreferenceSet(final MetisPreferenceManager pManager,
123                                  final OceanusBundleId pId) throws OceanusException {
124         this(pManager, pId.getValue());
125     }
126 
127     /**
128      * Constructor.
129      *
130      * @param pManager the preference manager
131      * @param pName    the set name
132      * @throws OceanusException on error
133      */
134     protected MetisPreferenceSet(final MetisPreferenceManager pManager,
135                                  final String pName) throws OceanusException {
136         /* Store name */
137         thePreferenceManager = pManager;
138         theName = pName;
139 
140         /* Allocate the fields */
141         theFields = MetisFieldSet.newFieldSet(this);
142 
143         /* Access the handle */
144         theHandle = deriveHandle();
145 
146         /* Create Event Manager */
147         theEventManager = new OceanusEventManager<>();
148 
149         /* Allocate the preference maps */
150         theNameMap = new LinkedHashMap<>();
151         theKeyMap = new LinkedHashMap<>();
152 
153         /* Access the active key names */
154         try {
155             theActive = theHandle.keys();
156         } catch (BackingStoreException e) {
157             throw new MetisDataException("Failed to access preferences", e);
158         }
159 
160         /* Define the preferences */
161         definePreferences();
162         autoCorrectPreferences();
163 
164         /* Store any changes */
165         storeChanges();
166 
167         /* Create the viewer record */
168         final MetisViewerManager myViewer = pManager.getViewer();
169         final MetisViewerEntry myParent = myViewer.getStandardEntry(MetisViewerStandardEntry.PREFERENCES);
170         theViewerEntry = myViewer.newEntry(myParent, theName);
171         theViewerEntry.setObject(this);
172     }
173 
174     /**
175      * Obtain the preference manager.
176      *
177      * @return the manager
178      */
179     public MetisPreferenceManager getPreferenceManager() {
180         return thePreferenceManager;
181     }
182 
183     @Override
184     public MetisFieldSetDef getDataFieldSet() {
185         return theFields;
186     }
187 
188     @Override
189     public String formatObject(final OceanusDataFormatter pFormatter) {
190         return theFields.getName();
191     }
192 
193     /**
194      * Declare preference.
195      *
196      * @param pPref the preference to declare
197      */
198     void declarePreference(final MetisPreferenceItem pPref) {
199         /* Create the DataField */
200         theFields.declareLocalField(pPref.getPreferenceName(), s -> pPref.getViewerValue());
201     }
202 
203     @Override
204     public OceanusEventRegistrar<MetisPreferenceEvent> getEventRegistrar() {
205         return theEventManager.getEventRegistrar();
206     }
207 
208     /**
209      * Hook to enable preferenceSets to define their preferences.
210      *
211      * @throws OceanusException on error
212      */
213     protected abstract void definePreferences() throws OceanusException;
214 
215     /**
216      * Hook to enable preferenceSets to autoCorrect their preferences.
217      * <p>
218      * This is used both to initialise preferencesSet defaults and to adjust the set when a value
219      * changes.
220      */
221     public abstract void autoCorrectPreferences();
222 
223     /**
224      * Obtain the name of the set.
225      *
226      * @return the name
227      */
228     public String getName() {
229         return theName;
230     }
231 
232     /**
233      * Is this a hidden preferenceSet?
234      *
235      * @return true/false
236      */
237     public boolean isHidden() {
238         return isHidden;
239     }
240 
241     /**
242      * Set this preferenceSet as hidden.
243      */
244     protected void setHidden() {
245         isHidden = true;
246     }
247 
248     /**
249      * Obtain the collection of preferences.
250      *
251      * @return the preferences
252      */
253     public Collection<MetisPreferenceItem> getPreferences() {
254         return theKeyMap.values();
255     }
256 
257     /**
258      * Set the focus.
259      */
260     public void setFocus() {
261         theViewerEntry.setFocus();
262     }
263 
264     /**
265      * Update the viewer entry.
266      */
267     public void updateViewerEntry() {
268         theViewerEntry.setObject(this);
269     }
270 
271     /**
272      * Derive handle for node.
273      *
274      * @return the class name
275      */
276     private Preferences deriveHandle() {
277         /* Obtain the class name */
278         final Class<?> myClass = this.getClass();
279         String myName = myClass.getCanonicalName();
280 
281         /* Obtain the package name */
282         final String myPackage = myClass.getPackage().getName();
283 
284         /* Strip off the package name */
285         myName = myName.substring(myPackage.length() + 1);
286 
287         /* Derive the handle */
288         final Preferences myHandle = Preferences.userNodeForPackage(myClass);
289         return myHandle.node(myName);
290     }
291 
292     /**
293      * Define new String preference.
294      *
295      * @param pKey the key for the preference
296      * @return the preference item
297      */
298     protected MetisStringPreference defineStringPreference(final MetisPreferenceKey pKey) {
299         /* Define the preference */
300         final MetisStringPreference myPref = new MetisStringPreference(this, pKey);
301 
302         /* Add it to the list of preferences */
303         definePreference(myPref);
304 
305         /* Return the preference */
306         return myPref;
307     }
308 
309     /**
310      * Define new File preference.
311      *
312      * @param pKey the key for the preference
313      * @return the preference item
314      */
315     protected MetisStringPreference defineFilePreference(final MetisPreferenceKey pKey) {
316         /* Define the preference */
317         final MetisStringPreference myPref = new MetisStringPreference(this, pKey, MetisPreferenceType.FILE);
318 
319         /* Add it to the list of preferences */
320         definePreference(myPref);
321 
322         /* Return the preference */
323         return myPref;
324     }
325 
326     /**
327      * Define new Directory preference.
328      *
329      * @param pKey the key for the preference
330      * @return the preference item
331      */
332     protected MetisStringPreference defineDirectoryPreference(final MetisPreferenceKey pKey) {
333         /* Define the preference */
334         final MetisStringPreference myPref = new MetisStringPreference(this, pKey, MetisPreferenceType.DIRECTORY);
335 
336         /* Add it to the list of preferences */
337         definePreference(myPref);
338 
339         /* Return the preference */
340         return myPref;
341     }
342 
343     /**
344      * Define new Colour preference.
345      *
346      * @param pKey the key for the preference
347      * @return the preference item
348      */
349     protected MetisStringPreference defineColorPreference(final MetisPreferenceKey pKey) {
350         /* Define the preference */
351         final MetisStringPreference myPref = new MetisStringPreference(this, pKey, MetisPreferenceType.COLOR);
352 
353         /* Add it to the list of preferences */
354         definePreference(myPref);
355 
356         /* Return the preference */
357         return myPref;
358     }
359 
360     /**
361      * Define new Integer preference.
362      *
363      * @param pKey the key for the preference
364      * @return the preference item
365      */
366     protected MetisIntegerPreference defineIntegerPreference(final MetisPreferenceKey pKey) {
367         /* Define the preference */
368         final MetisIntegerPreference myPref = new MetisIntegerPreference(this, pKey);
369 
370         /* Add it to the list of preferences */
371         definePreference(myPref);
372 
373         /* Return the preference */
374         return myPref;
375     }
376 
377     /**
378      * Define new Boolean preference.
379      *
380      * @param pKey the key for the preference
381      * @return the preference item
382      */
383     protected MetisBooleanPreference defineBooleanPreference(final MetisPreferenceKey pKey) {
384         /* Define the preference */
385         final MetisBooleanPreference myPref = new MetisBooleanPreference(this, pKey);
386 
387         /* Add it to the list of preferences */
388         definePreference(myPref);
389 
390         /* Return the preference */
391         return myPref;
392     }
393 
394     /**
395      * Define new Date preference.
396      *
397      * @param pKey the key for the preference
398      * @return the preference item
399      */
400     protected MetisDatePreference defineDatePreference(final MetisPreferenceKey pKey) {
401         /* Define the preference */
402         final MetisDatePreference myPref = new MetisDatePreference(this, pKey);
403 
404         /* Add it to the list of preferences */
405         definePreference(myPref);
406 
407         /* Return the preference */
408         return myPref;
409     }
410 
411     /**
412      * Define new Enum preference.
413      *
414      * @param <E>    the Enum type
415      * @param pKey   the key for the preference
416      * @param pClazz the Enum class
417      * @return the newly created preference
418      */
419     protected <E extends Enum<E>> MetisEnumPreference<E> defineEnumPreference(final MetisPreferenceKey pKey,
420                                                                               final Class<E> pClazz) {
421         /* Create the preference */
422         final MetisEnumPreference<E> myPref = new MetisEnumPreference<>(this, pKey, pClazz);
423 
424         /* Add it to the list of preferences */
425         definePreference(myPref);
426 
427         /* Return the preference */
428         return myPref;
429     }
430 
431     /**
432      * Define a preference for the node.
433      *
434      * @param pPreference the preference to define
435      */
436     protected void definePreference(final MetisPreferenceItem pPreference) {
437         /* Access the key of the preference */
438         final String myName = pPreference.getPreferenceName();
439 
440         /* Reject if the name is already present */
441         if (theNameMap.get(myName) != null) {
442             throw new IllegalArgumentException("preference "
443                     + myName
444                     + " is already defined");
445         }
446 
447         /* Add the preference to the map */
448         theNameMap.put(myName, pPreference);
449         theKeyMap.put(pPreference.getKey(), pPreference);
450     }
451 
452     /**
453      * Obtain preference by key.
454      *
455      * @param pKey the key of the preference
456      * @return the preference
457      */
458     public MetisPreferenceItem getPreference(final MetisPreferenceKey pKey) {
459         return theKeyMap.get(pKey);
460     }
461 
462     /**
463      * Obtain preference.
464      *
465      * @param pName the name of the preference
466      * @return the preference
467      */
468     protected MetisPreferenceItem getPreference(final String pName) {
469         return theNameMap.get(pName);
470     }
471 
472     /**
473      * Obtain String preference.
474      *
475      * @param pKey the key of the preference
476      * @return the String preference
477      */
478     public MetisStringPreference getStringPreference(final MetisPreferenceKey pKey) {
479         /* Access preference */
480         final MetisPreferenceItem myPref = getPreference(pKey);
481 
482         /* Reject if not found */
483         if (myPref == null) {
484             throw new IllegalArgumentException(ERROR_UNKNOWN
485                     + pKey);
486         }
487 
488         /* Reject if wrong type */
489         if (!(myPref instanceof MetisStringPreference)) {
490             throw new IllegalArgumentException(ERROR_INVALID
491                     + pKey);
492         }
493 
494         /* Return the preference */
495         return (MetisStringPreference) myPref;
496     }
497 
498     /**
499      * Obtain String value.
500      *
501      * @param pKey the key of the preference
502      * @return the String value
503      */
504     public String getStringValue(final MetisPreferenceKey pKey) {
505         /* Access preference */
506         final MetisStringPreference myPref = getStringPreference(pKey);
507 
508         /* Return the value */
509         return myPref.getValue();
510     }
511 
512     /**
513      * Obtain Integer preference.
514      *
515      * @param pKey the key of the preference
516      * @return the Integer preference
517      */
518     public MetisIntegerPreference getIntegerPreference(final MetisPreferenceKey pKey) {
519         /* Access preference */
520         final MetisPreferenceItem myPref = getPreference(pKey);
521 
522         /* Reject if not found */
523         if (myPref == null) {
524             throw new IllegalArgumentException(ERROR_UNKNOWN
525                     + pKey);
526         }
527 
528         /* Reject if wrong type */
529         if (!(myPref instanceof MetisPreferenceSet.MetisIntegerPreference)) {
530             throw new IllegalArgumentException(ERROR_INVALID
531                     + pKey);
532         }
533 
534         /* Return the preference */
535         return (MetisIntegerPreference) myPref;
536     }
537 
538     /**
539      * Obtain Integer value.
540      *
541      * @param pKey the key of the preference
542      * @return the Integer value
543      */
544     public Integer getIntegerValue(final MetisPreferenceKey pKey) {
545         /* Access preference */
546         final MetisIntegerPreference myPref = getIntegerPreference(pKey);
547 
548         /* Return the value */
549         return myPref.getValue();
550     }
551 
552     /**
553      * Obtain Boolean preference.
554      *
555      * @param pKey the key of the preference
556      * @return the Boolean preference
557      */
558     public MetisBooleanPreference getBooleanPreference(final MetisPreferenceKey pKey) {
559         /* Access preference */
560         final MetisPreferenceItem myPref = getPreference(pKey);
561 
562         /* Reject if not found */
563         if (myPref == null) {
564             throw new IllegalArgumentException(ERROR_UNKNOWN
565                     + pKey);
566         }
567 
568         /* Reject if wrong type */
569         if (!(myPref instanceof MetisBooleanPreference)) {
570             throw new IllegalArgumentException(ERROR_INVALID
571                     + pKey);
572         }
573 
574         /* Return the preference */
575         return (MetisBooleanPreference) myPref;
576     }
577 
578     /**
579      * Obtain Boolean value.
580      *
581      * @param pKey the key of the preference
582      * @return the Boolean value
583      */
584     public Boolean getBooleanValue(final MetisPreferenceKey pKey) {
585         /* Access preference */
586         final MetisBooleanPreference myPref = getBooleanPreference(pKey);
587 
588         /* Return the value */
589         return myPref.getValue();
590     }
591 
592     /**
593      * Obtain Date preference.
594      *
595      * @param pKey the key of the preference
596      * @return the Date preference
597      */
598     public MetisDatePreference getDatePreference(final MetisPreferenceKey pKey) {
599         /* Access preference */
600         final MetisPreferenceItem myPref = getPreference(pKey);
601 
602         /* Reject if not found */
603         if (myPref == null) {
604             throw new IllegalArgumentException(ERROR_UNKNOWN
605                     + pKey);
606         }
607 
608         /* Reject if wrong type */
609         if (!(myPref instanceof MetisDatePreference)) {
610             throw new IllegalArgumentException(ERROR_INVALID
611                     + pKey);
612         }
613 
614         /* Return the preference */
615         return (MetisDatePreference) myPref;
616     }
617 
618     /**
619      * Obtain Date value.
620      *
621      * @param pKey the key of the preference
622      * @return the Date value
623      */
624     public OceanusDate getDateValue(final MetisPreferenceKey pKey) {
625         /* Access preference */
626         final MetisDatePreference myPref = getDatePreference(pKey);
627 
628         /* Return the value */
629         return myPref.getValue();
630     }
631 
632     /**
633      * Obtain Enum preference.
634      *
635      * @param <E>    the EnumType
636      * @param pKey   the key of the preference
637      * @param pClazz the Enum class
638      * @return the Enum preference
639      */
640     public <E extends Enum<E>> MetisEnumPreference<E> getEnumPreference(final MetisPreferenceKey pKey,
641                                                                         final Class<E> pClazz) {
642         /* Access preference */
643         final MetisPreferenceItem myPref = getPreference(pKey);
644 
645         /* Reject if not found */
646         if (myPref == null) {
647             throw new IllegalArgumentException(ERROR_UNKNOWN
648                     + pKey);
649         }
650 
651         /* Reject if wrong type */
652         if (!(myPref instanceof MetisEnumPreference)) {
653             throw new IllegalArgumentException(ERROR_INVALID
654                     + pKey);
655         }
656 
657         /* Access as Enum preference */
658         @SuppressWarnings("unchecked") final MetisEnumPreference<E> myEnumPref = (MetisEnumPreference<E>) myPref;
659         if (!myEnumPref.theClazz.equals(pClazz)) {
660             throw new IllegalArgumentException(ERROR_INVALID
661                     + pKey);
662         }
663 
664         /* Return the preference */
665         return myEnumPref;
666     }
667 
668     /**
669      * Obtain Enum value.
670      *
671      * @param <E>    the EnumType
672      * @param pKey   the key of the preference
673      * @param pClazz the Enum class
674      * @return the Enum value
675      */
676     public <E extends Enum<E>> E getEnumValue(final MetisPreferenceKey pKey,
677                                               final Class<E> pClazz) {
678         /* Access preference */
679         final MetisEnumPreference<E> myPref = getEnumPreference(pKey, pClazz);
680 
681         /* Return the value */
682         return myPref.getValue();
683     }
684 
685     /**
686      * Reset all changes in this preference set.
687      */
688     public void resetChanges() {
689         /* Loop through all the preferences */
690         for (MetisPreferenceItem myPref : theKeyMap.values()) {
691             /* Reset the changes */
692             myPref.resetChanges();
693         }
694     }
695 
696     /**
697      * Store preference changes.
698      *
699      * @throws OceanusException on error
700      */
701     public final void storeChanges() throws OceanusException {
702         /* Loop through all the preferences */
703         for (MetisPreferenceItem myPref : theKeyMap.values()) {
704             /* Store any changes */
705             myPref.storePreference();
706         }
707 
708         /* Protect against exceptions */
709         try {
710             /* Flush the output */
711             theHandle.flush();
712 
713             /* Notify listeners */
714             theEventManager.fireEvent(MetisPreferenceEvent.PREFCHANGED);
715 
716         } catch (BackingStoreException e) {
717             throw new MetisDataException("Failed to flush preferences to store", e);
718         }
719     }
720 
721     /**
722      * Does the preference set have changes.
723      *
724      * @return true/false
725      */
726     public boolean hasChanges() {
727         /* Loop through all the preferences */
728         for (MetisPreferenceItem myPref : theKeyMap.values()) {
729             /* Check for changes */
730             if (myPref.isChanged()) {
731                 return true;
732             }
733         }
734 
735         /* Return no changes */
736         return false;
737     }
738 
739     /**
740      * Check whether a preference exists.
741      *
742      * @param pKey the key of the preference
743      * @return whether the preference already exists
744      */
745     protected boolean checkExists(final MetisPreferenceKey pKey) {
746         /* Obtain the name */
747         final String myKeyName = pKey.getName();
748 
749         /* Loop through all the keys */
750         for (String myName : theActive) {
751             /* If the name matches return true */
752             if (myName.equals(myKeyName)) {
753                 return true;
754             }
755         }
756 
757         /* return no match */
758         return false;
759     }
760 
761     /**
762      * Underlying preference item class.
763      */
764     public abstract static class MetisPreferenceItem {
765         /**
766          * preferenceSet.
767          */
768         private final MetisPreferenceSet theSet;
769 
770         /**
771          * preference Key.
772          */
773         private final MetisPreferenceKey theKey;
774 
775         /**
776          * preference Name.
777          */
778         private final String theName;
779 
780         /**
781          * Display Name.
782          */
783         private final String theDisplay;
784 
785         /**
786          * preference Type.
787          */
788         private final MetisPreferenceId theType;
789 
790         /**
791          * preference Value.
792          */
793         private Object theValue;
794 
795         /**
796          * New preference Value.
797          */
798         private Object theNewValue;
799 
800         /**
801          * Is there a change to the preference?
802          */
803         private boolean isChanged;
804 
805         /**
806          * Is the preference hidden?
807          */
808         private boolean isHidden;
809 
810         /**
811          * Constructor.
812          *
813          * @param pSet  the preference Set
814          * @param pKey  the key of the preference
815          * @param pType the type of the preference
816          */
817         protected MetisPreferenceItem(final MetisPreferenceSet pSet,
818                                       final MetisPreferenceKey pKey,
819                                       final MetisPreferenceId pType) {
820             /* Store parameters */
821             theSet = pSet;
822             theKey = pKey;
823             theType = pType;
824 
825             /* Obtain key details */
826             theName = pKey.getName();
827             theDisplay = pKey.getDisplay();
828 
829             /* Create the DataField */
830             theSet.declarePreference(this);
831         }
832 
833         /**
834          * Obtain viewer value.
835          *
836          * @return the value
837          */
838         Object getViewerValue() {
839             return isHidden
840                     ? null
841                     : getValue();
842         }
843 
844         /**
845          * Obtain the preferenceSet.
846          *
847          * @return the set
848          */
849         protected MetisPreferenceSet getSet() {
850             return theSet;
851         }
852 
853         /**
854          * Obtain the preference handle.
855          *
856          * @return the preference handle
857          */
858         protected Preferences getHandle() {
859             return theSet.theHandle;
860         }
861 
862         /**
863          * Obtain the key of the preference.
864          *
865          * @return the key of the preference
866          */
867         protected MetisPreferenceKey getKey() {
868             return theKey;
869         }
870 
871         /**
872          * Obtain the name of the preference.
873          *
874          * @return the name of the preference
875          */
876         protected String getPreferenceName() {
877             return theName;
878         }
879 
880         /**
881          * Obtain the display name of the preference.
882          *
883          * @return the display name of the preference
884          */
885         public String getDisplay() {
886             return theDisplay;
887         }
888 
889         /**
890          * Obtain the type of the preference.
891          *
892          * @return the type of the preference
893          */
894         public MetisPreferenceId getType() {
895             return theType;
896         }
897 
898         /**
899          * Obtain the value of the preference.
900          *
901          * @return the value of the preference
902          */
903         protected Object getValue() {
904             /* Return the active value */
905             return isChanged
906                     ? theNewValue
907                     : theValue;
908         }
909 
910         /**
911          * Is the preference available?
912          *
913          * @return true/false
914          */
915         public boolean isAvailable() {
916             return getValue() != null;
917         }
918 
919         /**
920          * Is the preference changed?
921          *
922          * @return true/false
923          */
924         public boolean isChanged() {
925             return isChanged;
926         }
927 
928         /**
929          * Is the preference hidden?
930          *
931          * @return true/false
932          */
933         public boolean isHidden() {
934             return isHidden;
935         }
936 
937         /**
938          * Set hidden.
939          *
940          * @param pHidden true/false
941          */
942         public void setHidden(final boolean pHidden) {
943             isHidden = pHidden;
944         }
945 
946         /**
947          * Set value.
948          *
949          * @param pValue the value
950          */
951         protected void setTheValue(final Object pValue) {
952             theValue = pValue;
953         }
954 
955         /**
956          * Set new value.
957          *
958          * @param pNewValue the new value
959          */
960         protected void setNewValue(final Object pNewValue) {
961             theNewValue = pNewValue;
962             isChanged = !MetisDataDifference.isEqual(theNewValue, theValue);
963         }
964 
965         /**
966          * Reset changes.
967          */
968         private void resetChanges() {
969             /* Reset change indicators */
970             theNewValue = null;
971             isChanged = false;
972         }
973 
974         /**
975          * Store preference.
976          *
977          * @throws OceanusException on error
978          */
979         private void storePreference() throws OceanusException {
980             /* If the preference has changed */
981             if (isChanged) {
982                 /* If we have a value */
983                 if (theNewValue != null) {
984                     /* Store the value */
985                     storeThePreference(theNewValue);
986 
987                     /* Note the new value and reset changes */
988                     setTheValue(theNewValue);
989 
990                     /* else no value */
991                 } else {
992                     /* remove the preference */
993                     getHandle().remove(theName);
994                 }
995 
996                 /* reset changes */
997                 resetChanges();
998             }
999         }
1000 
1001         /**
1002          * Store the value of the preference.
1003          *
1004          * @param pNewValue the new value to store
1005          * @throws OceanusException on error
1006          */
1007         protected abstract void storeThePreference(Object pNewValue) throws OceanusException;
1008     }
1009 
1010     /**
1011      * String preference.
1012      */
1013     public static class MetisStringPreference
1014             extends MetisPreferenceItem {
1015         /**
1016          * Constructor.
1017          *
1018          * @param pSet the preference Set
1019          * @param pKey the key of the preference
1020          */
1021         protected MetisStringPreference(final MetisPreferenceSet pSet,
1022                                         final MetisPreferenceKey pKey) {
1023             this(pSet, pKey, MetisPreferenceType.STRING);
1024         }
1025 
1026         /**
1027          * Constructor.
1028          *
1029          * @param pSet  the preference Set
1030          * @param pKey  the key of the preference
1031          * @param pType the type of the preference
1032          */
1033         private MetisStringPreference(final MetisPreferenceSet pSet,
1034                                       final MetisPreferenceKey pKey,
1035                                       final MetisPreferenceType pType) {
1036             /* Store name */
1037             super(pSet, pKey, pType);
1038 
1039             /* Check whether we have an existing value */
1040             if (pSet.checkExists(pKey)) {
1041                 /* Access the value */
1042                 final String myValue = getHandle().get(getPreferenceName(), null);
1043 
1044                 /* Set as initial value */
1045                 setTheValue(myValue);
1046             }
1047         }
1048 
1049         @Override
1050         public String getValue() {
1051             return (String) super.getValue();
1052         }
1053 
1054         /**
1055          * Set value.
1056          *
1057          * @param pNewValue the new value
1058          */
1059         public void setValue(final String pNewValue) {
1060             setNewValue(pNewValue);
1061         }
1062 
1063         @Override
1064         protected void storeThePreference(final Object pNewValue) {
1065             getHandle().put(getPreferenceName(), (String) pNewValue);
1066         }
1067     }
1068 
1069     /**
1070      * Integer preference.
1071      */
1072     public static class MetisIntegerPreference
1073             extends MetisPreferenceItem {
1074         /**
1075          * The minimum value.
1076          */
1077         private Integer theMinimum;
1078 
1079         /**
1080          * The maximum value.
1081          */
1082         private Integer theMaximum;
1083 
1084         /**
1085          * Constructor.
1086          *
1087          * @param pSet the preference Set
1088          * @param pKey the key of the preference
1089          */
1090         protected MetisIntegerPreference(final MetisPreferenceSet pSet,
1091                                          final MetisPreferenceKey pKey) {
1092             /* Store name */
1093             super(pSet, pKey, MetisPreferenceType.INTEGER);
1094 
1095             /* Check whether we have an existing value */
1096             if (pSet.checkExists(pKey)) {
1097                 /* Access the value */
1098                 final int myValue = getHandle().getInt(getPreferenceName(), -1);
1099 
1100                 /* Set as initial value */
1101                 setTheValue(myValue);
1102             }
1103         }
1104 
1105         @Override
1106         public Integer getValue() {
1107             return (Integer) super.getValue();
1108         }
1109 
1110         /**
1111          * Obtain the minimum value.
1112          *
1113          * @return the minimum
1114          */
1115         public Integer getMinimum() {
1116             return theMinimum;
1117         }
1118 
1119         /**
1120          * Obtain the maximum value.
1121          *
1122          * @return the maximum
1123          */
1124         public Integer getMaximum() {
1125             return theMaximum;
1126         }
1127 
1128         /**
1129          * Set value.
1130          *
1131          * @param pNewValue the new value
1132          */
1133         public void setValue(final Integer pNewValue) {
1134             setNewValue(pNewValue);
1135         }
1136 
1137         /**
1138          * Set range.
1139          *
1140          * @param pMinimum the minimum value
1141          * @param pMaximum the maximum value
1142          */
1143         public void setRange(final Integer pMinimum,
1144                              final Integer pMaximum) {
1145             theMinimum = pMinimum;
1146             theMaximum = pMaximum;
1147         }
1148 
1149         /**
1150          * Validate the range.
1151          *
1152          * @return true/false
1153          */
1154         public boolean validate() {
1155             if (isAvailable()) {
1156                 final Integer myValue = getValue();
1157                 if ((theMinimum != null)
1158                         && theMinimum > myValue) {
1159                     return false;
1160                 }
1161                 if ((theMaximum != null)
1162                         && theMaximum < myValue) {
1163                     return false;
1164                 }
1165             }
1166             return true;
1167         }
1168 
1169         @Override
1170         protected void storeThePreference(final Object pNewValue) {
1171             getHandle().putInt(getPreferenceName(), (Integer) pNewValue);
1172         }
1173     }
1174 
1175     /**
1176      * Boolean preference.
1177      */
1178     public static class MetisBooleanPreference
1179             extends MetisPreferenceItem {
1180         /**
1181          * Constructor.
1182          *
1183          * @param pSet the preference Set
1184          * @param pKey the key of the preference
1185          */
1186         protected MetisBooleanPreference(final MetisPreferenceSet pSet,
1187                                          final MetisPreferenceKey pKey) {
1188             /* Store name */
1189             super(pSet, pKey, MetisPreferenceType.BOOLEAN);
1190 
1191             /* Check whether we have an existing value */
1192             if (pSet.checkExists(pKey)) {
1193                 /* Access the value */
1194                 final boolean myValue = getHandle().getBoolean(getPreferenceName(), false);
1195 
1196                 /* Set as initial value */
1197                 setTheValue(myValue
1198                         ? Boolean.TRUE
1199                         : Boolean.FALSE);
1200             }
1201         }
1202 
1203         @Override
1204         public Boolean getValue() {
1205             return (Boolean) super.getValue();
1206         }
1207 
1208         /**
1209          * Set value.
1210          *
1211          * @param pNewValue the new value
1212          */
1213         public void setValue(final Boolean pNewValue) {
1214             Boolean myNewValue = pNewValue;
1215 
1216             /* Take a copy if not null */
1217             if (myNewValue != null) {
1218                 myNewValue = myNewValue
1219                         ? Boolean.TRUE
1220                         : Boolean.FALSE;
1221             }
1222 
1223             /* Set the new value */
1224             setNewValue(myNewValue);
1225         }
1226 
1227         @Override
1228         protected void storeThePreference(final Object pNewValue) {
1229             getHandle().putBoolean(getPreferenceName(), (Boolean) pNewValue);
1230         }
1231     }
1232 
1233     /**
1234      * Date preference.
1235      */
1236     public static class MetisDatePreference
1237             extends MetisPreferenceItem {
1238         /**
1239          * Constructor.
1240          *
1241          * @param pSet the preference Set
1242          * @param pKey the key of the preference
1243          */
1244         protected MetisDatePreference(final MetisPreferenceSet pSet,
1245                                       final MetisPreferenceKey pKey) {
1246             /* Store name */
1247             super(pSet, pKey, MetisPreferenceType.DATE);
1248 
1249             /* Check whether we have an existing value */
1250             if (pSet.checkExists(pKey)) {
1251                 /* Access the value */
1252                 final String myValue = getHandle().get(getPreferenceName(), null);
1253 
1254                 /* Parse the Date */
1255                 final OceanusDate myDate = new OceanusDate(myValue);
1256 
1257                 /* Set as initial value */
1258                 setTheValue(myDate);
1259             }
1260         }
1261 
1262         @Override
1263         public OceanusDate getValue() {
1264             return (OceanusDate) super.getValue();
1265         }
1266 
1267         /**
1268          * Set value.
1269          *
1270          * @param pNewValue the new value
1271          */
1272         public void setValue(final OceanusDate pNewValue) {
1273             OceanusDate myNewValue = pNewValue;
1274 
1275             /* Take a copy if not null */
1276             if (myNewValue != null) {
1277                 myNewValue = new OceanusDate(myNewValue);
1278             }
1279 
1280             /* Set the new value */
1281             setNewValue(myNewValue);
1282         }
1283 
1284         @Override
1285         protected void storeThePreference(final Object pNewValue) {
1286             getHandle().put(getPreferenceName(), ((OceanusDate) pNewValue).toString());
1287         }
1288     }
1289 
1290     /**
1291      * Enum preference.
1292      *
1293      * @param <E> the Enum type
1294      */
1295     public static class MetisEnumPreference<E extends Enum<E>>
1296             extends MetisPreferenceItem {
1297         /**
1298          * The enum class.
1299          */
1300         private final Class<E> theClazz;
1301 
1302         /**
1303          * The enum values.
1304          */
1305         private final E[] theValues;
1306 
1307         /**
1308          * The filter.
1309          */
1310         private Predicate<E> theFilter;
1311 
1312         /**
1313          * Constructor.
1314          *
1315          * @param pSet   the preference Set
1316          * @param pKey   the key of the preference
1317          * @param pClazz the class of the preference
1318          */
1319         public MetisEnumPreference(final MetisPreferenceSet pSet,
1320                                    final MetisPreferenceKey pKey,
1321                                    final Class<E> pClazz) {
1322             /* Store name */
1323             super(pSet, pKey, MetisPreferenceType.ENUM);
1324 
1325             /* Store the class */
1326             theClazz = pClazz;
1327             theValues = theClazz.getEnumConstants();
1328 
1329             /* Set null filter */
1330             setFilter(null);
1331 
1332             /* Check whether we have an existing value */
1333             if (pSet.checkExists(pKey)) {
1334                 /* Access the value */
1335                 final String myValue = getHandle().get(getPreferenceName(), null);
1336 
1337                 /* Set the value */
1338                 final E myEnum = findValue(myValue);
1339                 setTheValue(myEnum);
1340             }
1341         }
1342 
1343         @Override
1344         public E getValue() {
1345             return theClazz.cast(super.getValue());
1346         }
1347 
1348         /**
1349          * Obtain the values of the preference.
1350          *
1351          * @return the values of the preference
1352          */
1353         public E[] getValues() {
1354             return Arrays.copyOf(theValues, theValues.length);
1355         }
1356 
1357         /**
1358          * Obtain the filter.
1359          *
1360          * @return the filter
1361          */
1362         public Predicate<E> getFilter() {
1363             return theFilter;
1364         }
1365 
1366         /**
1367          * Set value.
1368          *
1369          * @param pNewValue the new value
1370          * @return the Enum value
1371          */
1372         private E findValue(final String pNewValue) {
1373             /* Loop through the Enum constants */
1374             for (E myEnum : theValues) {
1375                 /* If we match */
1376                 if (pNewValue.equals(myEnum.name())) {
1377                     /* Return the value */
1378                     return myEnum;
1379                 }
1380             }
1381 
1382             /* Return invalid value */
1383             return null;
1384         }
1385 
1386         /**
1387          * Set value.
1388          *
1389          * @param pNewValue the new value
1390          */
1391         public final void setValue(final String pNewValue) {
1392             /* Convert to enum and set */
1393             final E myEnum = findValue(pNewValue);
1394             setNewValue(myEnum);
1395         }
1396 
1397         /**
1398          * Set value.
1399          *
1400          * @param pNewValue the new value
1401          */
1402         public void setValue(final E pNewValue) {
1403             setNewValue(pNewValue);
1404         }
1405 
1406         /**
1407          * Set filter.
1408          *
1409          * @param pFilter the new filter
1410          */
1411         public void setFilter(final Predicate<E> pFilter) {
1412             theFilter = theFilter == null
1413                     ? p -> true
1414                     : pFilter;
1415         }
1416 
1417         @Override
1418         protected void storeThePreference(final Object pNewValue) {
1419             getHandle().put(getPreferenceName(), theClazz.cast(pNewValue).name());
1420         }
1421     }
1422 }