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 }