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