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.data;
18  
19  import io.github.tonywasher.joceanus.oceanus.resource.OceanusBundleId;
20  
21  import java.util.Arrays;
22  
23  /**
24   * Difference enum and utility.
25   */
26  public enum MetisDataDifference {
27      /**
28       * Identical.
29       */
30      IDENTICAL,
31  
32      /**
33       * Value Changed.
34       */
35      DIFFERENT,
36  
37      /**
38       * Security Changed.
39       */
40      SECURITY;
41  
42      /**
43       * Difference interface.
44       */
45      @FunctionalInterface
46      public interface MetisDataDiffers {
47          /**
48           * Test for difference with another object.
49           *
50           * @param pThat the other object
51           * @return the difference
52           */
53          MetisDataDifference differs(Object pThat);
54      }
55  
56      /**
57       * The String name.
58       */
59      private String theName;
60  
61      @Override
62      public String toString() {
63          /* If we have not yet loaded the name */
64          if (theName == null) {
65              /* Load the name */
66              theName = bundleIdFoDifference(this).getValue();
67          }
68  
69          /* return the name */
70          return theName;
71      }
72  
73      /**
74       * Is there differences?
75       *
76       * @return true/false
77       */
78      public boolean isDifferent() {
79          return !IDENTICAL.equals(this);
80      }
81  
82      /**
83       * Is there no differences?
84       *
85       * @return true/false
86       */
87      public boolean isIdentical() {
88          return !isDifferent();
89      }
90  
91      /**
92       * Is there value differences?
93       *
94       * @return true/false
95       */
96      public boolean isValueChanged() {
97          return DIFFERENT.equals(this);
98      }
99  
100     /**
101      * Is there security differences?
102      *
103      * @return true/false
104      */
105     public boolean isSecurityChanged() {
106         return SECURITY.equals(this);
107     }
108 
109     /**
110      * Combine Differences.
111      *
112      * @param pThat the difference to combine
113      * @return the combined difference
114      */
115     public MetisDataDifference combine(final MetisDataDifference pThat) {
116         return switch (this) {
117             case IDENTICAL -> pThat;
118             case SECURITY -> (pThat == DIFFERENT)
119                     ? pThat
120                     : this;
121             default -> this;
122         };
123     }
124 
125     /**
126      * Determine whether two Generic objects differ.
127      *
128      * @param pCurr The current object
129      * @param pNew  The new object
130      * @return the Difference between the objects
131      */
132     public static MetisDataDifference difference(final Object pCurr,
133                                                  final Object pNew) {
134         /* Handle identity */
135         if (pCurr == pNew) {
136             return IDENTICAL;
137         }
138 
139         /* Neither value can be null */
140         if (pCurr == null
141                 || pNew == null) {
142             return DIFFERENT;
143         }
144 
145         /* Handle class differences */
146         if (!pCurr.getClass().equals(pNew.getClass())) {
147             return DIFFERENT;
148         }
149 
150         /* Handle differs support */
151         if (pCurr instanceof MetisDataDiffers myDiffers) {
152             return myDiffers.differs(pNew);
153         }
154 
155         /* Handle Standard cases */
156         return pCurr.equals(pNew)
157                 ? IDENTICAL
158                 : DIFFERENT;
159     }
160 
161     /**
162      * Determine whether two Generic objects are equal.
163      *
164      * @param pCurr The current object
165      * @param pNew  The new object
166      * @return true/false
167      */
168     public static boolean isEqual(final Object pCurr,
169                                   final Object pNew) {
170         /* Handle identity */
171         if (pCurr == pNew) {
172             return true;
173         }
174 
175         /* Neither value can be null */
176         if (pCurr == null
177                 || pNew == null) {
178             return false;
179         }
180 
181         /* Handle class differences */
182         final Class<?> myClass = pCurr.getClass();
183         if (!myClass.equals(pNew.getClass())) {
184             return false;
185         }
186 
187         /* Handle arrays */
188         if (myClass.isArray()) {
189             /* Handle special cases for efficiency */
190             if (pCurr instanceof byte[] ba) {
191                 return Arrays.equals(ba, (byte[]) pNew);
192             }
193             if (pCurr instanceof char[] ca) {
194                 return Arrays.equals(ca, (char[]) pNew);
195             }
196 
197             /* Handle generic arrays */
198             return Arrays.equals((Object[]) pCurr, (Object[]) pNew);
199         }
200 
201         /* Handle Standard cases */
202         return pCurr.equals(pNew);
203     }
204 
205     /**
206      * Compare two similar objects for order.
207      *
208      * @param <X>   the object type
209      * @param pCurr The current object
210      * @param pNew  The new object
211      * @return order
212      */
213     public static <X extends Comparable<? super X>> int compareObject(final X pCurr,
214                                                                       final X pNew) {
215         /* Handle identity */
216         if (pCurr == pNew) {
217             return 0;
218         }
219 
220         /* Pass the call on, defaulting null to the end of the list */
221         return compareObject(pCurr, pNew, true);
222     }
223 
224     /**
225      * Compare two similar objects for order.
226      *
227      * @param <X>       the object type
228      * @param pCurr     The current object
229      * @param pNew      The new object
230      * @param pNullLast is Null at end of list?
231      * @return order
232      */
233     public static <X extends Comparable<? super X>> int compareObject(final X pCurr,
234                                                                       final X pNew,
235                                                                       final boolean pNullLast) {
236         /* Handle identity */
237         if (pCurr == pNew) {
238             return 0;
239         }
240 
241         /* Handle positioning of nulls */
242         if (pCurr == null) {
243             return pNullLast
244                     ? 1
245                     : -1;
246         }
247         if (pNew == null) {
248             return pNullLast
249                     ? -1
250                     : 1;
251         }
252 
253         /* Both non-Null, so pass the call on */
254         return pCurr.compareTo(pNew);
255     }
256 
257     /**
258      * Obtain the resource bundleId for the difference.
259      *
260      * @param pDiffer the difference
261      * @return the resource bundleId
262      */
263     private static OceanusBundleId bundleIdFoDifference(final MetisDataDifference pDiffer) {
264         /* Create the map and return it */
265         return switch (pDiffer) {
266             case IDENTICAL -> MetisDataResource.DIFFERENCE_IDENTICAL;
267             case DIFFERENT -> MetisDataResource.DIFFERENCE_DIFFERENT;
268             case SECURITY -> MetisDataResource.DIFFERENCE_SECURITY;
269             default -> throw new IllegalArgumentException();
270         };
271     }
272 }