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.base.OceanusException;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
22  import io.github.tonywasher.joceanus.metis.data.MetisDataType;
23  import io.github.tonywasher.joceanus.metis.exc.MetisDataException;
24  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldDef;
25  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldSetDef;
26  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldVersionedDef;
27  
28  import java.util.Iterator;
29  
30  /**
31   * Set of dataValues.
32   */
33  public class MetisFieldVersionValues {
34      /**
35       * The hash value for deletion flag.
36       */
37      private static final int DELETION_HASH = 3;
38  
39      /**
40       * The versioned error.
41       */
42      protected static final String ERROR_NOTVERSIONED = "Field is not versioned";
43  
44      /**
45       * The valueType error.
46       */
47      private static final String ERROR_VALUETYPE = "Invalid valueType";
48  
49      /**
50       * The item to which the valueSet belongs.
51       */
52      private final MetisFieldVersionedItem theItem;
53  
54      /**
55       * The fields for this valueSet.
56       */
57      private final MetisFieldSetDef theFields;
58  
59      /**
60       * The number of values.
61       */
62      private final int theNumValues;
63  
64      /**
65       * The values.
66       */
67      private final Object[] theValues;
68  
69      /**
70       * Version # of the values.
71       */
72      private int theVersion;
73  
74      /**
75       * Is this valueSet a record of a deletion event.
76       */
77      private boolean isDeletion;
78  
79      /**
80       * Constructor.
81       *
82       * @param pItem the associated item
83       */
84      protected MetisFieldVersionValues(final MetisFieldVersionedItem pItem) {
85          /* Create the values array and initialise to null */
86          theItem = pItem;
87          theFields = pItem.getDataFieldSet();
88          theNumValues = theFields.getNumVersioned();
89          theValues = new Object[theNumValues];
90      }
91  
92      /**
93       * Obtain the field definitions.
94       *
95       * @return the field definitions
96       */
97      public MetisFieldSetDef getFields() {
98          return theFields;
99      }
100 
101     /**
102      * Obtain the underlying item.
103      *
104      * @return the item
105      */
106     protected MetisFieldVersionedItem getItem() {
107         return theItem;
108     }
109 
110     /**
111      * Obtain the version # of the values.
112      *
113      * @return the version #
114      */
115     public int getVersion() {
116         return theVersion;
117     }
118 
119     /**
120      * Set the version # of the values.
121      *
122      * @param pVersion the version #
123      */
124     public void setVersion(final int pVersion) {
125         theVersion = pVersion;
126     }
127 
128     /**
129      * Determine if this object is a record of a deletion event.
130      *
131      * @return true/false
132      */
133     public boolean isDeletion() {
134         return isDeletion;
135     }
136 
137     /**
138      * Adjust deletion flag.
139      *
140      * @param pDeletion true/false
141      */
142     public void setDeletion(final boolean pDeletion) {
143         isDeletion = pDeletion;
144     }
145 
146     /**
147      * Clone this ValueSet.
148      *
149      * @return the cloned set
150      */
151     public MetisFieldVersionValues cloneIt() {
152         /* Create the valueSet and initialise to existing values */
153         final MetisFieldVersionValues mySet = new MetisFieldVersionValues(theItem);
154         mySet.copyFrom(this);
155         return mySet;
156     }
157 
158     /**
159      * Initialise values from a previous set.
160      *
161      * @param pPrevious the previous valueSet
162      */
163     public void copyFrom(final MetisFieldVersionValues pPrevious) {
164         /* Copy deletion flag */
165         isDeletion = pPrevious.isDeletion();
166 
167         /* Determine the copyLength */
168         int myCopyLen = pPrevious.theNumValues;
169         if (myCopyLen > theNumValues) {
170             myCopyLen = theNumValues;
171         }
172 
173         /* Copy values */
174         if (myCopyLen > 0) {
175             System.arraycopy(pPrevious.theValues, 0, theValues, 0, myCopyLen);
176         }
177     }
178 
179     /**
180      * Set the value.
181      *
182      * @param pFieldId the fieldId
183      * @param pValue   the value
184      * @throws OceanusException on error
185      */
186     public void setValue(final MetisDataFieldId pFieldId,
187                          final Object pValue) throws OceanusException {
188         final MetisFieldDef myField = theFields.getField(pFieldId);
189         setValue(myField, pValue);
190     }
191 
192     /**
193      * Set the value.
194      *
195      * @param pField the field
196      * @param pValue the value
197      * @throws OceanusException on error
198      */
199     public void setValue(final MetisFieldDef pField,
200                          final Object pValue) throws OceanusException {
201         /* Reject if not in valueSet */
202         if (!(pField instanceof MetisFieldVersionedDef)) {
203             throw new IllegalArgumentException(ERROR_NOTVERSIONED);
204         }
205 
206         /* Check value type */
207         checkValueType(pField, pValue);
208 
209         /* Store the value */
210         theValues[((MetisFieldVersionedDef) pField).getIndex()] = pValue;
211     }
212 
213     /**
214      * Set the value.
215      *
216      * @param pFieldId the fieldId
217      * @param pValue   the value
218      */
219     public void setUncheckedValue(final MetisDataFieldId pFieldId,
220                                   final Object pValue) {
221         final MetisFieldDef myField = theFields.getField(pFieldId);
222         setUncheckedValue(myField, pValue);
223     }
224 
225     /**
226      * Set the unchecked value.
227      *
228      * @param pField the field
229      * @param pValue the value
230      */
231     public void setUncheckedValue(final MetisFieldDef pField,
232                                   final Object pValue) {
233         /* Reject if not in valueSet */
234         if (!(pField instanceof MetisFieldVersionedDef)) {
235             throw new IllegalArgumentException(ERROR_NOTVERSIONED);
236         }
237 
238         /* Store the value */
239         theValues[((MetisFieldVersionedDef) pField).getIndex()] = pValue;
240     }
241 
242     /**
243      * Get the value.
244      *
245      * @param pFieldId the fieldId
246      * @return the value
247      */
248     public Object getValue(final MetisDataFieldId pFieldId) {
249         final MetisFieldDef myField = theFields.getField(pFieldId);
250         return getValue(myField);
251     }
252 
253     /**
254      * Get the value.
255      *
256      * @param pField the field
257      * @return the value
258      */
259     public Object getValue(final MetisFieldDef pField) {
260         /* Reject if not in valueSet */
261         if (!(pField instanceof MetisFieldVersionedDef)) {
262             throw new IllegalArgumentException(ERROR_NOTVERSIONED);
263         }
264 
265         /* Return the value */
266         return theValues[((MetisFieldVersionedDef) pField).getIndex()];
267     }
268 
269     /**
270      * Get the value.
271      *
272      * @param <X>      the required type
273      * @param pFieldId the fieldId
274      * @param pClazz   the class
275      * @return the value
276      */
277     public <X> X getValue(final MetisDataFieldId pFieldId,
278                           final Class<X> pClazz) {
279         final MetisFieldDef myField = theFields.getField(pFieldId);
280         return getValue(myField, pClazz);
281     }
282 
283     /**
284      * Get the value as an object type.
285      *
286      * @param <X>    the required type
287      * @param pField the field
288      * @param pClazz the class
289      * @return the value
290      */
291     public <X> X getValue(final MetisFieldDef pField,
292                           final Class<X> pClazz) {
293         /* Access the value */
294         final Object myValue = getValue(pField);
295 
296         /* Return the value */
297         return pClazz.cast(myValue);
298     }
299 
300     @Override
301     public boolean equals(final Object pThat) {
302         /* Handle the trivial cases */
303         if (this == pThat) {
304             return true;
305         }
306         if (pThat == null) {
307             return false;
308         }
309 
310         /* Make sure that the object is a ValueSet */
311         if (pThat.getClass() != this.getClass()) {
312             return false;
313         }
314 
315         /* Access the object as a ValueSet */
316         final MetisFieldVersionValues mySet = (MetisFieldVersionValues) pThat;
317 
318         /* Check for deletion flag and # of values */
319         if (isDeletion != mySet.isDeletion
320                 || theNumValues != mySet.theNumValues) {
321             return false;
322         }
323 
324         /* Loop through the values */
325         final Iterator<MetisFieldDef> myIterator = theFields.fieldIterator();
326         while (myIterator.hasNext()) {
327             /* Ignore non-equality and non-versioned fields */
328             final MetisFieldDef myField = myIterator.next();
329             if (!(myField instanceof MetisFieldVersionedDef myVersioned)
330                     || !myVersioned.isEquality()) {
331                 continue;
332             }
333 
334             /* Not equal if the value is different */
335             final int iIndex = myVersioned.getIndex();
336             if (MetisDataDifference.difference(theValues[iIndex], mySet.theValues[iIndex]).isDifferent()) {
337                 return false;
338             }
339         }
340 
341         /* Identical if all fields match */
342         return true;
343     }
344 
345     @Override
346     public int hashCode() {
347         /* Use deletion flag in hash Code */
348         int iHashCode = isDeletion
349                 ? DELETION_HASH
350                 : 1;
351 
352         /* Loop through the values */
353         final Iterator<MetisFieldDef> myIterator = theFields.fieldIterator();
354         while (myIterator.hasNext()) {
355             /* Ignore non-equality and non-versioned fields */
356             final MetisFieldDef myField = myIterator.next();
357             if (!(myField instanceof MetisFieldVersionedDef myVersioned)
358                     || !myVersioned.isEquality()) {
359                 continue;
360             }
361 
362             /* Adjust existing hash */
363             iHashCode *= MetisFieldSet.HASH_PRIME;
364 
365             /* Access value and add hash if non-null */
366             final int iIndex = myVersioned.getIndex();
367             final Object o = theValues[iIndex];
368             if (o != null) {
369                 iHashCode += o.hashCode();
370             }
371         }
372 
373         /* Return the hash */
374         return iHashCode;
375     }
376 
377     /**
378      * Check for differences.
379      *
380      * @param pOriginal the object to check for differences
381      * @return the difference
382      */
383     public MetisDataDifference differs(final MetisFieldVersionValues pOriginal) {
384         boolean isSecureDiff = false;
385 
386         /* Check for deletion flag and # of values */
387         if (isDeletion != pOriginal.isDeletion
388                 || theNumValues != pOriginal.theNumValues) {
389             return MetisDataDifference.DIFFERENT;
390         }
391 
392         /* Loop through the values */
393         final Iterator<MetisFieldDef> myIterator = theFields.fieldIterator();
394         while (myIterator.hasNext()) {
395             /* Ignore non-equality and non-versioned fields */
396             final MetisFieldDef myField = myIterator.next();
397             if (!(myField instanceof MetisFieldVersionedDef myVersioned)
398                     || !myVersioned.isEquality()) {
399                 continue;
400             }
401 
402             /* Check the field */
403             final int iIndex = myVersioned.getIndex();
404             final MetisDataDifference myDiff = MetisDataDifference.difference(theValues[iIndex], pOriginal.theValues[iIndex]);
405             if (myDiff == MetisDataDifference.DIFFERENT) {
406                 return myDiff;
407             }
408             if (myDiff == MetisDataDifference.SECURITY) {
409                 isSecureDiff = true;
410             }
411         }
412 
413         /* Determine the difference */
414         return isSecureDiff
415                 ? MetisDataDifference.SECURITY
416                 : MetisDataDifference.IDENTICAL;
417     }
418 
419     /**
420      * Check for a difference in a particular field.
421      *
422      * @param pFieldId  the field to check for differences
423      * @param pOriginal the original value set
424      * @return the difference
425      */
426     public MetisDataDifference fieldChanged(final MetisDataFieldId pFieldId,
427                                             final MetisFieldVersionValues pOriginal) {
428         final MetisFieldDef myField = theFields.getField(pFieldId);
429         return fieldChanged(myField, pOriginal);
430     }
431 
432     /**
433      * Check for a difference in a particular field.
434      *
435      * @param pField    the field to check for differences
436      * @param pOriginal the original value set
437      * @return the difference
438      */
439     public MetisDataDifference fieldChanged(final MetisFieldDef pField,
440                                             final MetisFieldVersionValues pOriginal) {
441         /* No difference if field does not exist, is not-equality or is not versioned */
442         if (!(pField instanceof MetisFieldVersionedDef myVersioned)
443                 || !myVersioned.isEquality()) {
444             return MetisDataDifference.IDENTICAL;
445         }
446 
447         /* Determine the difference */
448         final int iIndex = myVersioned.getIndex();
449         return MetisDataDifference.difference(theValues[iIndex], pOriginal.theValues[iIndex]);
450     }
451 
452     /**
453      * Check the value.
454      *
455      * @param pField the field
456      * @param pValue the value
457      * @throws OceanusException on error
458      */
459     protected void checkValueType(final MetisFieldDef pField,
460                                   final Object pValue) throws OceanusException {
461         /* Null/String is always allowed */
462         if (pValue == null
463                 || pValue instanceof String) {
464             return;
465         }
466 
467         /* Integer is allowed for Link type */
468         final MetisDataType myDataType = pField.getDataType();
469         if (MetisDataType.LINK.equals(myDataType)
470                 && pValue instanceof Integer) {
471             return;
472         }
473 
474         /* Long is allowed for LinkPair type */
475         if (MetisDataType.LINKPAIR.equals(myDataType)
476                 && pValue instanceof Long) {
477             return;
478         }
479 
480         /* Check expected dataType */
481         final Class<?> myClass = myDataType.getDataTypeClass();
482         final boolean bAllowed = myClass == null || myClass.isInstance(pValue);
483 
484         /* If we are not allowed */
485         if (!bAllowed) {
486             throw new MetisDataException(ERROR_VALUETYPE);
487         }
488     }
489 }