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 }