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