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