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