View Javadoc
1   /*
2    * Prometheus: Application 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.prometheus.views;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
21  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
22  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar.OceanusEventProvider;
23  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
24  import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogManager;
25  import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogger;
26  import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
27  import io.github.tonywasher.joceanus.metis.data.MetisDataEditState;
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.list.MetisListKey;
31  import io.github.tonywasher.joceanus.metis.ui.MetisErrorPanel;
32  import io.github.tonywasher.joceanus.metis.viewer.MetisViewerErrorList;
33  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
34  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataList;
35  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataList.PrometheusDataListSet;
36  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
37  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet;
38  
39  import java.util.Iterator;
40  import java.util.LinkedHashMap;
41  import java.util.Map;
42  
43  /**
44   * Provides control of a set of update-able DataLists.
45   */
46  public class PrometheusEditSet
47          implements MetisFieldItem, OceanusEventProvider<PrometheusDataEvent>, PrometheusDataListSet {
48      /**
49       * Report fields.
50       */
51      @SuppressWarnings("rawtypes")
52      private static final MetisFieldSet<PrometheusEditSet> FIELD_DEFS = MetisFieldSet.newFieldSet(PrometheusEditSet.class);
53  
54      /*
55       * Declare Fields.
56       */
57      static {
58          FIELD_DEFS.declareLocalField(PrometheusDataResource.DATASET_VERSION, PrometheusEditSet::getVersion);
59      }
60  
61      /**
62       * Logger.
63       */
64      private static final OceanusLogger LOGGER = OceanusLogManager.getLogger(PrometheusEditSet.class);
65  
66      /**
67       * The Event Manager.
68       */
69      private final OceanusEventManager<PrometheusDataEvent> theEventManager;
70  
71      /**
72       * Report fields.
73       */
74      @SuppressWarnings("rawtypes")
75      private final MetisFieldSet<PrometheusEditSet> theLocalFields;
76  
77      /**
78       * The entry map.
79       */
80      private final Map<MetisListKey, PrometheusEditEntry<?>> theMap;
81  
82      /**
83       * The DataControl.
84       */
85      private final PrometheusDataControl theControl;
86  
87      /**
88       * The DataSet.
89       */
90      private PrometheusDataSet theDataSet;
91  
92      /**
93       * The version.
94       */
95      private int theVersion;
96  
97      /**
98       * Are we editing?
99       */
100     private Boolean itemEditing = Boolean.FALSE;
101 
102     /**
103      * Constructor for an update list.
104      *
105      * @param pControl the Data Control
106      */
107     public PrometheusEditSet(final PrometheusDataControl pControl) {
108         this(pControl, pControl.getData());
109     }
110 
111     /**
112      * Constructor for an update list.
113      *
114      * @param pControl the Data Control
115      * @param pDataSet the DataSet
116      */
117     public PrometheusEditSet(final PrometheusDataControl pControl,
118                              final PrometheusDataSet pDataSet) {
119         /* Store the Control */
120         theControl = pControl;
121         theDataSet = pDataSet;
122 
123         /* Create event manager */
124         theEventManager = new OceanusEventManager<>();
125 
126         /* Create local fields */
127         theLocalFields = MetisFieldSet.newFieldSet(this);
128 
129         /* Create the map */
130         theMap = new LinkedHashMap<>();
131     }
132 
133     @SuppressWarnings("rawtypes")
134     @Override
135     public MetisFieldSet<PrometheusEditSet> getDataFieldSet() {
136         return theLocalFields;
137     }
138 
139     @Override
140     public String formatObject(final OceanusDataFormatter pFormatter) {
141         return getDataFieldSet().getName();
142     }
143 
144     @Override
145     public OceanusEventRegistrar<PrometheusDataEvent> getEventRegistrar() {
146         return theEventManager.getEventRegistrar();
147     }
148 
149     /**
150      * Set editing flag.
151      *
152      * @param pFlag the editing flag
153      */
154     public void setEditing(final Boolean pFlag) {
155         itemEditing = pFlag;
156     }
157 
158     /**
159      * Is the item editing?
160      *
161      * @return true/false
162      */
163     public Boolean isEditing() {
164         return itemEditing;
165     }
166 
167     /**
168      * Obtain the version.
169      *
170      * @return the version
171      */
172     public int getVersion() {
173         return theVersion;
174     }
175 
176     /**
177      * Obtain the dataSet.
178      *
179      * @return the dataSet
180      */
181     public PrometheusDataSet getDataSet() {
182         return theDataSet;
183     }
184 
185     /**
186      * Set the dataSet.
187      *
188      * @param pDataSet the dataSet
189      */
190     public void setDataSet(final PrometheusDataSet pDataSet) {
191         theDataSet = pDataSet;
192     }
193 
194     /**
195      * Register an entry for a class.
196      *
197      * @param <T>       the object type
198      * @param pDataType the data type
199      * @return the list class entry
200      */
201     public <T extends PrometheusDataItem> PrometheusEditEntry<T> registerType(final MetisListKey pDataType) {
202         /* Locate any existing entry */
203         @SuppressWarnings("unchecked") final PrometheusEditEntry<T> myEntry = (PrometheusEditEntry<T>) theMap.computeIfAbsent(pDataType, t -> {
204             /* Not found , so add it */
205             final PrometheusEditEntry<T> myNewEntry = new PrometheusEditEntry<>(t);
206             theLocalFields.declareLocalField(myNewEntry.getName(), n -> myNewEntry);
207             return myNewEntry;
208         });
209 
210         /* Update list to null and return */
211         myEntry.setDataList(null);
212         return myEntry;
213     }
214 
215     /**
216      * Obtain the list for a class.
217      * <p>
218      * Will look first for the list in the updateSet and then in the underlying data.
219      *
220      * @param <L>       the list type
221      * @param pDataType the data type
222      * @param pClass    the list class
223      * @return the list
224      */
225     @Override
226     public <L extends PrometheusDataList<?>> L getDataList(final MetisListKey pDataType,
227                                                            final Class<L> pClass) {
228         /* Locate an existing entry */
229         final PrometheusEditEntry<?> myEntry = theMap.get(pDataType);
230 
231         /* Cast correctly */
232         return myEntry != null
233                 ? pClass.cast(myEntry.getDataList())
234                 : theDataSet.getDataList(pDataType, pClass);
235     }
236 
237     @Override
238     public boolean hasDataType(final MetisListKey pDataType) {
239         return theMap.containsKey(pDataType);
240     }
241 
242     /**
243      * Set the editEntry for a type.
244      *
245      * @param <T>       the dataType
246      * @param pDataType the data type
247      * @param pList     the list
248      */
249     public <T extends PrometheusDataItem> void setEditEntryList(final MetisListKey pDataType,
250                                                                 final PrometheusDataList<T> pList) {
251         @SuppressWarnings("unchecked") final PrometheusEditEntry<T> myEntry = (PrometheusEditEntry<T>) theMap.get(pDataType);
252         myEntry.setDataList(pList);
253     }
254 
255     /**
256      * Obtain an iterator over the listKeys.
257      *
258      * @return the iterator
259      */
260     public Iterator<MetisListKey> keyIterator() {
261         return theMap.keySet().iterator();
262     }
263 
264     /**
265      * Obtain an iterator over the listKeys.
266      *
267      * @return the iterator
268      */
269     public Iterator<PrometheusEditEntry<?>> listIterator() {
270         return theMap.values().iterator();
271     }
272 
273     /**
274      * Increment Version.
275      */
276     public void incrementVersion() {
277         /* Obtain the active profile */
278         final OceanusProfile myTask = theControl.getActiveTask();
279         final OceanusProfile mySubTask = myTask == null
280                 ? theControl.getNewProfile("incrementVersion")
281                 : myTask.startTask("incrementVersion");
282 
283         /* Increment the version */
284         theVersion++;
285 
286         /* Loop through the items in the list */
287         for (PrometheusEditEntry<?> myEntry : theMap.values()) {
288             /* Access list */
289             final PrometheusDataList<?> myDataList = myEntry.getDataList();
290 
291             /* Increment the version if the list exists */
292             if (myDataList != null) {
293                 /* Note the new step */
294                 mySubTask.startTask(myDataList.listName());
295 
296                 /* Set the new version */
297                 myDataList.setVersion(theVersion);
298 
299                 /* postProcess */
300                 if (Boolean.FALSE.equals(itemEditing)) {
301                     myDataList.postProcessOnUpdate();
302                 }
303             }
304         }
305 
306         /* Complete the task */
307         mySubTask.end();
308     }
309 
310     /**
311      * Rewind items to the required version.
312      *
313      * @param pVersion the version to rewind to
314      */
315     private void rewindToVersion(final int pVersion) {
316         /* Obtain the active profile */
317         final OceanusProfile myTask = theControl.getActiveTask();
318         OceanusProfile mySubTask = myTask.startTask("reWindToVersion");
319 
320         /* Record the version */
321         theVersion = pVersion;
322 
323         /* Loop through the items in the list */
324         Iterator<PrometheusEditEntry<?>> myIterator = theMap.values().iterator();
325         while (myIterator.hasNext()) {
326             /* Access list */
327             final PrometheusEditEntry<?> myEntry = myIterator.next();
328             final PrometheusDataList<?> myDataList = myEntry.getDataList();
329 
330             /* If the list exists */
331             if (myDataList != null) {
332                 /* Note the new step */
333                 mySubTask.startTask(myDataList.listName());
334 
335                 /* Rewind the version */
336                 myDataList.rewindToVersion(theVersion);
337             }
338         }
339 
340         /* Need to validate after full reWind to avoid false errors */
341         mySubTask = myTask.startTask("postProcess");
342         myIterator = theMap.values().iterator();
343         while (myIterator.hasNext()) {
344             /* Access list */
345             final PrometheusEditEntry<?> myEntry = myIterator.next();
346             final PrometheusDataList<?> myDataList = myEntry.getDataList();
347 
348             /* If the list exists */
349             if (myDataList != null) {
350                 /* Note the new step */
351                 mySubTask.startTask(myDataList.listName());
352 
353                 /* postProcess */
354                 if (Boolean.FALSE.equals(itemEditing)) {
355                     myDataList.postProcessOnUpdate();
356                 }
357             }
358         }
359 
360         /* Note the new step */
361         mySubTask = myTask.startTask("Notify");
362 
363         /* Fire that we have rewound the updateSet */
364         theEventManager.fireEvent(PrometheusDataEvent.DATACHANGED);
365 
366         /* Complete the task */
367         mySubTask.end();
368     }
369 
370     /**
371      * Undo changes in a viewSet.
372      */
373     private void undoLastChange() {
374         /* Ignore if we have no changes */
375         if (theVersion == 0) {
376             return;
377         }
378 
379         /* Decrement version */
380         theVersion--;
381 
382         /* Rewind to the version */
383         rewindToVersion(theVersion);
384     }
385 
386     /**
387      * Reset changes in a viewSet.
388      */
389     private void resetChanges() {
390         /* Ignore if we have no changes */
391         if (theVersion == 0) {
392             return;
393         }
394 
395         /* Decrement version */
396         theVersion = 0;
397 
398         /* Rewind to the version */
399         rewindToVersion(theVersion);
400     }
401 
402     /**
403      * Apply changes in a ViewSet into the core data.
404      */
405     private void applyChanges() {
406         /* Obtain the active profile */
407         final OceanusProfile myTask = theControl.getActiveTask();
408         final OceanusProfile mySubTask = myTask.startTask("applyChanges");
409 
410         /* Validate the changes */
411         validate();
412 
413         /* Reject request if there are errors */
414         if (hasErrors()) {
415             /* We have finished */
416             mySubTask.startTask("Notify");
417 
418             /* Fire that we have rewound the updateSet */
419             theEventManager.fireEvent(PrometheusDataEvent.DATACHANGED);
420 
421             /* Complete the task */
422             mySubTask.end();
423             return;
424         }
425 
426         /* Apply the changes */
427         boolean bSuccess = prepareChanges();
428 
429         /* analyse the data */
430         if (bSuccess) {
431             /* Analyse the applied changes */
432             bSuccess = theControl.analyseData(false);
433         }
434 
435         /* If we were successful */
436         if (bSuccess) {
437             /* Commit the changes */
438             commitChanges();
439 
440             /* Refresh views */
441             theControl.refreshViews();
442 
443             /* else we failed */
444         } else {
445             /* RollBack the changes */
446             rollBackChanges();
447 
448             /* Re-analyse the data */
449             theControl.analyseData(true);
450         }
451 
452         /* Complete the task */
453         mySubTask.end();
454     }
455 
456     /**
457      * Prepare changes in a ViewSet back into the core data.
458      *
459      * @return success true/false
460      */
461     private boolean prepareChanges() {
462         /* Obtain the active profile */
463         final OceanusProfile myTask = theControl.getActiveTask();
464         final OceanusProfile mySubTask = myTask.startTask("prepareChanges");
465         boolean bSuccess = true;
466 
467         /* Protect against exceptions */
468         try {
469             /* Loop through the items in the list */
470             for (PrometheusEditEntry<?> myEntry : theMap.values()) {
471                 /* Note the new step */
472                 mySubTask.startTask(myEntry.getName());
473 
474                 /* Prepare changes for the entry */
475                 myEntry.prepareChanges();
476             }
477 
478         } catch (OceanusException e) {
479             LOGGER.error("Failed to prepare changes", e);
480             bSuccess = false;
481         }
482 
483         /* Complete the task */
484         mySubTask.end();
485         return bSuccess;
486     }
487 
488     /**
489      * Commit changes in a ViewSet back into the core data.
490      */
491     private void commitChanges() {
492         /* Obtain the active profile */
493         final OceanusProfile myTask = theControl.getActiveTask();
494         final OceanusProfile mySubTask = myTask.startTask("commitChanges");
495 
496         /* Loop through the items in the list */
497         for (PrometheusEditEntry<?> myEntry : theMap.values()) {
498             /* Note the new step */
499             mySubTask.startTask(myEntry.getName());
500 
501             /* Commit changes for the entry */
502             myEntry.commitChanges();
503         }
504 
505         /* Increment the version and notify listeners */
506         theControl.incrementVersion();
507         theVersion = 0;
508 
509         /* Complete the task */
510         mySubTask.end();
511     }
512 
513     /**
514      * RollBack changes in a ViewSet back into the core data.
515      */
516     private void rollBackChanges() {
517         /* Obtain the active profile */
518         final OceanusProfile myTask = theControl.getActiveTask();
519         final OceanusProfile mySubTask = myTask.startTask("rollBackChanges");
520 
521         /* Loop through the items in the list */
522         for (PrometheusEditEntry<?> myEntry : theMap.values()) {
523             /* Note the new step */
524             mySubTask.startTask(myEntry.getName());
525 
526             /* RollBack changes for the entry */
527             myEntry.rollBackChanges();
528         }
529 
530         /* Complete the task */
531         mySubTask.end();
532     }
533 
534     /**
535      * Has this UpdateSet got updates?
536      *
537      * @return true/false
538      */
539     public boolean hasUpdates() {
540         /* We have changes if version is non-zero */
541         return theVersion != 0;
542     }
543 
544     /**
545      * Has this UpdateSet got errors?
546      *
547      * @return true/false
548      */
549     public boolean hasErrors() {
550         /* Loop through the items in the list */
551         for (PrometheusEditEntry<?> myEntry : theMap.values()) {
552             /* Access entry */
553             final PrometheusDataList<?> myDataList = myEntry.getDataList();
554 
555             /* Determine whether there are errors */
556             if (myDataList != null && myDataList.hasErrors()) {
557                 return true;
558             }
559         }
560 
561         /* Return to caller */
562         return false;
563     }
564 
565     /**
566      * Validate the updateSet.
567      */
568     public void validate() {
569         /* Obtain the active profile */
570         final OceanusProfile myTask = theControl.getActiveTask();
571         final OceanusProfile mySubTask = myTask.startTask("validate");
572 
573         /* Loop through the items in the list */
574         for (PrometheusEditEntry<?> myEntry : theMap.values()) {
575             /* Access list */
576             final PrometheusDataList<?> myDataList = myEntry.getDataList();
577 
578             /* if list exists */
579             if (myDataList != null) {
580                 /* Note the new step */
581                 mySubTask.startTask(myDataList.listName());
582 
583                 /* Validate */
584                 myDataList.validate();
585             }
586         }
587 
588         /* Complete the task */
589         mySubTask.end();
590     }
591 
592     /**
593      * Get the edit state of this set of tables.
594      *
595      * @return the edit state
596      */
597     public MetisDataEditState getEditState() {
598         /* Loop through the items in the list */
599         final Iterator<PrometheusEditEntry<?>> myIterator = theMap.values().iterator();
600         MetisDataEditState myState = MetisDataEditState.CLEAN;
601         while (myIterator.hasNext()) {
602             /* Access list */
603             final PrometheusEditEntry<?> myEntry = myIterator.next();
604             final PrometheusDataList<?> myDataList = myEntry.getDataList();
605 
606             /* Combine states if list exists */
607             if (myDataList != null) {
608                 myState = myState.combineState(myDataList.getEditState());
609             }
610         }
611 
612         /* Return the state */
613         return myState;
614     }
615 
616     /**
617      * Process Save command.
618      *
619      * @param pCmd   the command.
620      * @param pError the error panel
621      */
622     public void processCommand(final PrometheusUIEvent pCmd,
623                                final MetisErrorPanel pError) {
624         /* Create a new profile */
625         final OceanusProfile myTask = theControl.getNewProfile("EditCommand");
626 
627         /* Switch on command */
628         switch (pCmd) {
629             case OK:
630                 applyChanges();
631                 break;
632             case UNDO:
633                 undoLastChange();
634                 break;
635             case RESET:
636                 resetChanges();
637                 break;
638             default:
639                 break;
640         }
641 
642         /* Access any error */
643         final MetisViewerErrorList myErrors = theControl.getErrors();
644 
645         /* Show the error */
646         if (!myErrors.isEmpty()) {
647             pError.setErrors(myErrors);
648         }
649 
650         /* Complete the task */
651         myTask.end();
652     }
653 
654     /**
655      * Process Edit command.
656      *
657      * @param pCmd     the command.
658      * @param pVersion the version
659      * @param pError   the error panel
660      */
661     public void processEditCommand(final PrometheusUIEvent pCmd,
662                                    final int pVersion,
663                                    final MetisErrorPanel pError) {
664         /* Create a new profile */
665         final OceanusProfile myTask = theControl.getNewProfile("ItemCommand");
666 
667         /* Switch on command */
668         switch (pCmd) {
669             case OK:
670                 condenseHistory(pVersion);
671                 break;
672             case UNDO:
673                 undoLastChange();
674                 break;
675             case REWIND:
676                 rewindToVersion(pVersion);
677                 break;
678             default:
679                 break;
680         }
681 
682         /* Access any error */
683         final MetisViewerErrorList myErrors = theControl.getErrors();
684 
685         /* Show the error */
686         if (!myErrors.isEmpty()) {
687             pError.setErrors(myErrors);
688         }
689 
690         /* Complete the task */
691         myTask.end();
692     }
693 
694     /**
695      * Condense history.
696      *
697      * @param pNewVersion the new maximum version
698      */
699     private void condenseHistory(final int pNewVersion) {
700         /* Obtain the active profile */
701         final OceanusProfile myTask = theControl.getActiveTask();
702         final OceanusProfile mySubTask = myTask.startTask("condenseHistory");
703 
704         /* Loop through the items in the list */
705         for (PrometheusEditEntry<?> myEntry : theMap.values()) {
706             /* Access list */
707             final PrometheusDataList<?> myDataList = myEntry.getDataList();
708 
709             /* Condense history in the list */
710             if (myDataList != null) {
711                 /* Note the new step */
712                 final OceanusProfile myListTask = mySubTask.startTask(myDataList.listName());
713                 myListTask.startTask("Condense");
714 
715                 /* Condense history */
716                 myDataList.condenseHistory(pNewVersion);
717 
718                 /* postProcess */
719                 if (Boolean.FALSE.equals(itemEditing)) {
720                     myListTask.startTask("postProcess");
721                     myDataList.postProcessOnUpdate();
722                 }
723             }
724         }
725 
726         /* Store version */
727         theVersion = pNewVersion;
728 
729         /* Fire that we have added to updateSet */
730         theEventManager.fireEvent(PrometheusDataEvent.DATACHANGED);
731 
732         /* Complete the task */
733         mySubTask.end();
734     }
735 }