View Javadoc
1   /*
2    * Metis: Java Data Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.metis.field;
18  
19  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataEditState;
22  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataDeletableItem;
23  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
24  import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
25  import io.github.tonywasher.joceanus.metis.data.MetisDataState;
26  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldTableItem;
27  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldUpdatableItem;
28  import io.github.tonywasher.joceanus.metis.field.MetisFieldValidation.MetisFieldError;
29  
30  /**
31   * Data Version Control.
32   */
33  public abstract class MetisFieldVersionedItem
34          implements MetisFieldTableItem, MetisDataDeletableItem, MetisFieldUpdatableItem {
35      /**
36       * Report fields.
37       */
38      private static final MetisFieldVersionedSet<MetisFieldVersionedItem> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(MetisFieldVersionedItem.class);
39  
40      /*
41       * FieldIds.
42       */
43      static {
44          FIELD_DEFS.declareLocalField(MetisDataResource.DATA_ID, MetisFieldVersionedItem::getIndexedId);
45          FIELD_DEFS.declareLocalField(MetisDataResource.DATA_ITEMTYPE, MetisFieldVersionedItem::getItemType);
46          FIELD_DEFS.declareLocalField(MetisDataResource.DATA_VERSION, MetisFieldVersionedItem::getVersion);
47          FIELD_DEFS.declareLocalField(MetisDataResource.DATA_DELETED, MetisFieldVersionedItem::isDeleted);
48          FIELD_DEFS.declareLocalField(MetisDataResource.DATA_STATE, MetisFieldVersionedItem::getState);
49          FIELD_DEFS.declareLocalField(MetisDataResource.DATA_EDITSTATE, MetisFieldVersionedItem::getEditState);
50          FIELD_DEFS.declareLocalField(MetisDataResource.DATA_HISTORY, MetisFieldVersionedItem::getValuesHistory);
51          FIELD_DEFS.declareLocalField(MetisDataResource.DATA_ERRORS, MetisFieldVersionedItem::getValidation);
52      }
53  
54      /**
55       * The history of values for this object.
56       */
57      private final MetisFieldVersionHistory theHistory;
58  
59      /**
60       * The Item Validation.
61       */
62      private final MetisFieldValidation theValidation;
63  
64      /**
65       * The id.
66       */
67      private Integer theId;
68  
69      /**
70       * The itemType.
71       */
72      private MetisFieldItemType theItemType;
73  
74      /**
75       * The editState.
76       */
77      private MetisDataEditState theEditState;
78  
79      /**
80       * Constructor.
81       */
82      protected MetisFieldVersionedItem() {
83          /* Allocate the history */
84          final MetisFieldVersionValues myValues = newVersionValues();
85          theHistory = new MetisFieldVersionHistory(myValues);
86  
87          /* Allocate the validation */
88          theValidation = new MetisFieldValidation();
89          theEditState = MetisDataEditState.CLEAN;
90      }
91  
92      /**
93       * Constructor.
94       *
95       * @param pValues the initial values
96       */
97      protected MetisFieldVersionedItem(final MetisFieldVersionValues pValues) {
98          /* Allocate the history */
99          theHistory = new MetisFieldVersionHistory(pValues);
100 
101         /* Allocate the validation */
102         theValidation = new MetisFieldValidation();
103     }
104 
105     /**
106      * Obtain new version values.
107      *
108      * @return new values
109      */
110     protected MetisFieldVersionValues newVersionValues() {
111         return new MetisFieldVersionValues(this);
112     }
113 
114     @Override
115     public Integer getIndexedId() {
116         return theId;
117     }
118 
119     /**
120      * Set Id.
121      *
122      * @param pId the Id
123      */
124     public void setIndexedId(final Integer pId) {
125         theId = pId;
126     }
127 
128     /**
129      * Obtain the itemType.
130      *
131      * @return the item type
132      */
133     public MetisFieldItemType getItemType() {
134         return theItemType;
135     }
136 
137     /**
138      * Set ItemType.
139      *
140      * @param pItemType the itemType
141      */
142     public void setItemType(final MetisFieldItemType pItemType) {
143         theItemType = pItemType;
144     }
145 
146     /**
147      * Obtain the State of item.
148      *
149      * @return the state
150      */
151     public MetisDataState getState() {
152         return determineState();
153     }
154 
155     /**
156      * Obtain the editState of item.
157      *
158      * @return the editState
159      */
160     public MetisDataEditState getEditState() {
161         return theEditState;
162     }
163 
164     @Override
165     public MetisFieldVersionHistory getValuesHistory() {
166         return theHistory;
167     }
168 
169     /**
170      * Obtain the Validation.
171      *
172      * @return the validation
173      */
174     public MetisFieldValidation getValidation() {
175         return theValidation;
176     }
177 
178     @Override
179     public MetisFieldVersionValues getValues() {
180         return theHistory.getValueSet();
181     }
182 
183     @Override
184     public MetisFieldVersionValues getOriginalValues() {
185         return theHistory.getOriginalValues();
186     }
187 
188     /**
189      * Obtain the Version of item.
190      *
191      * @return the version
192      */
193     public Integer getVersion() {
194         return getValues().getVersion();
195     }
196 
197     /**
198      * Obtain the original Version of item.
199      *
200      * @return the version
201      */
202     public Integer getOriginalVersion() {
203         return getOriginalValues().getVersion();
204     }
205 
206     @Override
207     public boolean isDeleted() {
208         return getValues().isDeletion();
209     }
210 
211     @Override
212     public void setDeleted(final boolean pFlag) {
213         /* If the state has changed */
214         if (pFlag != isDeleted()) {
215             /* Push history and set flag */
216             pushHistory();
217             getValues().setDeletion(pFlag);
218         }
219     }
220 
221     @Override
222     public MetisFieldSetDef getDataFieldSet() {
223         return FIELD_DEFS;
224     }
225 
226     @Override
227     public String formatObject(final OceanusDataFormatter pFormatter) {
228         return FIELD_DEFS.getName();
229     }
230 
231     /**
232      * Set the Edit State.
233      *
234      * @param pState the Edit Status
235      */
236     protected void setEditState(final MetisDataEditState pState) {
237         theEditState = pState;
238     }
239 
240     /**
241      * Determine whether the item has Errors.
242      *
243      * @return <code>true/false</code>
244      */
245     public boolean hasErrors() {
246         return theEditState == MetisDataEditState.ERROR;
247     }
248 
249     /**
250      * Determine whether the item has Changes.
251      *
252      * @return <code>true/false</code>
253      */
254     public boolean hasChanges() {
255         return theEditState != MetisDataEditState.CLEAN;
256     }
257 
258     /**
259      * Determine whether the item is Valid.
260      *
261      * @return <code>true/false</code>
262      */
263     public boolean isValid() {
264         return theEditState == MetisDataEditState.CLEAN
265                 || theEditState == MetisDataEditState.VALID;
266     }
267 
268     @Override
269     public boolean hasErrors(final MetisDataFieldId pFieldId) {
270         return pFieldId != null
271                 && theValidation.hasErrors(pFieldId);
272     }
273 
274     /**
275      * Add an error for this item.
276      *
277      * @param pError   the error text
278      * @param pFieldId the associated field
279      */
280     public void addError(final String pError,
281                          final MetisDataFieldId pFieldId) {
282         /* Set edit state and add the error */
283         theEditState = MetisDataEditState.ERROR;
284         theValidation.addError(pError, pFieldId);
285     }
286 
287     /**
288      * Clear all errors for this item.
289      */
290     public void clearErrors() {
291         theEditState = getValues().getVersion() > 0
292                 ? MetisDataEditState.DIRTY
293                 : MetisDataEditState.CLEAN;
294         theValidation.clearErrors();
295     }
296 
297     /**
298      * Get the first error element for an item.
299      *
300      * @return the first error (or <code>null</code>)
301      */
302     public MetisFieldError getFirstError() {
303         return theValidation.getFirst();
304     }
305 
306     @Override
307     public String getFieldErrors(final MetisDataFieldId pField) {
308         return pField != null
309                 ? theValidation.getFieldErrors(pField)
310                 : null;
311     }
312 
313     @Override
314     public String getFieldErrors(final MetisDataFieldId[] pFields) {
315         final MetisFieldSetDef myFieldSet = getDataFieldSet();
316         final MetisFieldDef[] myFields = new MetisFieldDef[pFields.length];
317         for (int i = 0; i < pFields.length; i++) {
318             myFields[i] = myFieldSet.getField(pFields[i]);
319         }
320         return theValidation.getFieldErrors(myFields);
321     }
322 
323     /**
324      * Initialise the current values.
325      *
326      * @param pValues the current values
327      */
328     public void setValues(final MetisFieldVersionValues pValues) {
329         theHistory.setValues(pValues);
330         adjustState();
331     }
332 
333     @Override
334     public void pushHistory() {
335         final int myVersion = getNextVersion();
336         theHistory.pushHistory(myVersion);
337     }
338 
339     /**
340      * Push history to a specific version.
341      *
342      * @param pVersion the version
343      */
344     public void pushHistory(final int pVersion) {
345         theHistory.pushHistory(pVersion);
346     }
347 
348     @Override
349     public void popHistory() {
350         theHistory.popTheHistory();
351     }
352 
353     /**
354      * popItem from the history if equal to current.
355      *
356      * @return was a change made
357      */
358     public boolean maybePopHistory() {
359         return theHistory.maybePopHistory();
360     }
361 
362     /**
363      * Is there any history.
364      *
365      * @return whether there are entries in the history list
366      */
367     public boolean hasHistory() {
368         return theHistory.hasHistory();
369     }
370 
371     /**
372      * Clear history.
373      */
374     public void clearHistory() {
375         theHistory.clearHistory();
376         adjustState();
377     }
378 
379     /**
380      * Reset history.
381      */
382     public void resetHistory() {
383         theHistory.resetHistory();
384         adjustState();
385     }
386 
387     /**
388      * Set history explicitly.
389      *
390      * @param pBase the base item
391      */
392     public void setHistory(final MetisFieldVersionValues pBase) {
393         theHistory.setHistory(pBase);
394         adjustState();
395     }
396 
397     @Override
398     public boolean checkForHistory() {
399         return theHistory.maybePopHistory();
400     }
401 
402     /**
403      * Condense history.
404      *
405      * @param pNewVersion the new maximum version
406      */
407     public void condenseHistory(final int pNewVersion) {
408         theHistory.condenseHistory(pNewVersion);
409     }
410 
411     /**
412      * Rewind item to the required version.
413      *
414      * @param pVersion the version to rewind to
415      */
416     public void rewindToVersion(final int pVersion) {
417         /* Loop while version is too high */
418         while (getValues().getVersion() > pVersion) {
419             /* Pop history */
420             getValuesHistory().popTheHistory();
421         }
422     }
423 
424     /**
425      * Determines whether a particular field has changed.
426      *
427      * @param pField the field
428      * @return the difference
429      */
430     public MetisDataDifference fieldChanged(final MetisDataFieldId pField) {
431         final MetisFieldDef myField = getDataFieldSet().getField(pField);
432         return theHistory.fieldChanged(myField);
433     }
434 
435     /**
436      * Determines whether a particular field has changed.
437      *
438      * @param pField the field
439      * @return the difference
440      */
441     public MetisDataDifference fieldChanged(final MetisFieldDef pField) {
442         return theHistory.fieldChanged(pField);
443     }
444 
445     /**
446      * Adjust State of item.
447      */
448     public void adjustState() {
449         theEditState = determineEditState();
450     }
451 
452     /**
453      * Determine dataState of item.
454      *
455      * @return the dataState
456      */
457     private MetisDataState determineState() {
458         final MetisFieldVersionValues myCurr = getValues();
459         final MetisFieldVersionValues myOriginal = getOriginalValues();
460 
461         /* If we are a new element */
462         if (myOriginal.getVersion() > 0) {
463             /* Return status */
464             return myCurr.isDeletion()
465                     ? MetisDataState.DELNEW
466                     : MetisDataState.NEW;
467         }
468 
469         /* If we have no changes we are CLEAN */
470         if (myCurr.getVersion() == 0) {
471             return MetisDataState.CLEAN;
472         }
473 
474         /* If we are deleted return so */
475         if (myCurr.isDeletion()) {
476             return MetisDataState.DELETED;
477         }
478 
479         /* We must have changed */
480         return MetisDataState.CHANGED;
481     }
482 
483     /**
484      * Determine editState of item.
485      *
486      * @return the editState
487      */
488     private MetisDataEditState determineEditState() {
489         /* If we have errors */
490         if (theValidation.hasErrors()) {
491             /* Return status */
492             return MetisDataEditState.ERROR;
493         }
494 
495         /* If we have no changes we are CLEAN */
496         return getVersion() == 0
497                 ? MetisDataEditState.CLEAN
498                 : MetisDataEditState.DIRTY;
499     }
500 
501     /**
502      * Obtain a versioned field.
503      *
504      * @param pId the field id
505      * @return the versioned field
506      */
507     public MetisFieldVersionedDef getVersionedField(final MetisDataFieldId pId) {
508         final MetisFieldDef myField = getDataFieldSet().getField(pId);
509         return myField instanceof MetisFieldVersionedDef myVersioned
510                 ? myVersioned
511                 : null;
512     }
513 }