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 }