Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
BasicPart |
|
| 3.2058823529411766;3.206 |
1 | /* | |
2 | * Copyright (c) 2009, Swiss Federal Department of Defence Civil Protection and Sport | |
3 | * (http://www.vbs.admin.ch) | |
4 | * Copyright (c) 2009, Ergon Informatik AG (http://www.ergon.ch) | |
5 | * All rights reserved. | |
6 | * | |
7 | * Licensed under the Open Permis License which accompanies this distribution, | |
8 | * and is available at http://www.openpermis.org/BSDlicenceKent.txt | |
9 | */ | |
10 | package org.openpermis.policy.bean.basic; | |
11 | ||
12 | import static org.openpermis.policy.bean.basic.BasicUtilities.equalObjects; | |
13 | import static org.openpermis.policy.bean.basic.BasicUtilities.multiHashCode; | |
14 | ||
15 | import java.net.URI; | |
16 | import java.util.HashMap; | |
17 | import java.util.Map; | |
18 | ||
19 | import org.openpermis.policy.Identifiable; | |
20 | import org.openpermis.policy.Nameable; | |
21 | import org.openpermis.policy.Part; | |
22 | import org.openpermis.policy.PartProblemReporter; | |
23 | import org.openpermis.policy.PartProblemReporter.ProblemMessage; | |
24 | ||
25 | /** | |
26 | * Abstract base class for policy parts. | |
27 | * <p><strong>Mix-in Interfaces</strong></p> | |
28 | * <p>This base class provides support for the mix-in interfaces:</p> | |
29 | * <ul> | |
30 | * <li>{@link Nameable}</li> | |
31 | * <li>{@link Identifiable}</li> | |
32 | * </ul> | |
33 | * <p>To create an implementation that features one of the mix-in interfaces listed above simply | |
34 | * implement the interface in your {@code AbstractPart} extension, the base class will take care | |
35 | * of the mix-in interface implementation details.</p> | |
36 | * <p><strong>State of Extensions</strong></p> | |
37 | * <p>If your part implementation features additional state make sure to provide implementations | |
38 | * for the following methods:</p> | |
39 | * <ul> | |
40 | * <li>{@link #isPartValid(PartProblemReporter)}</li> | |
41 | * <li>{@link #equalPart(BasicPart)}</li> | |
42 | * <li>{@link #comparablePart(BasicPart)}</li> | |
43 | * <li>{@link #partHashCode()}</li> | |
44 | * <li>{@link #appendPartDetails(StringBuilder)}</li> | |
45 | * </ul> | |
46 | * <p>(If your identity part does not have further state there is no need to provide | |
47 | * implementations of the methods listed above. The default implementations will guarantee | |
48 | * that your identity part implementation works as expected.)</p> | |
49 | * <p><strong>Note:</strong> If your implementation contains lists or sets of part beans make | |
50 | * sure they are compared using {@link BasicUtilities#equalLists(java.util.List, java.util.List)} | |
51 | * and the hash code is calculated using {@link BasicUtilities#listHashCode(java.util.List)}. | |
52 | * These implementations disregard the list item order.</p> | |
53 | * <p><strong>Getters/Setters</strong></p> | |
54 | * <p>Follow the JavaBean property specification when implementing getters and setters, e.g. if | |
55 | * your part features a value called {@code myValue} then provide {@code MyType getMyValue()} and | |
56 | * {@code setMyValue(Type myValue)}.</p> | |
57 | * <p>Make sure collection classes are cloned before you assign them to your internal state, | |
58 | * otherwise the caller can modify the collection from outside again.</p> | |
59 | * @since 0.1.0 | |
60 | */ | |
61 | public abstract class BasicPart | |
62 | implements Part | |
63 | { | |
64 | ||
65 | //---- Static | |
66 | ||
67 | /** | |
68 | * @since 0.1.0 | |
69 | */ | |
70 | private static final long serialVersionUID = 5635383950293028314L; | |
71 | ||
72 | /** | |
73 | * The URI consisting of an empty string. | |
74 | * @see #isIdentityValid(PartProblemReporter) | |
75 | * @since 0.1.0 | |
76 | */ | |
77 | 1 | private static final URI EMPTY_URI = URI.create(""); |
78 | ||
79 | //---- State | |
80 | ||
81 | /** | |
82 | * The identity of this part. | |
83 | * <p>Only used if the mix-in interface {@link Identifiable} is implemented.</p> | |
84 | * @see #hasPartIdentity() | |
85 | * @since 0.1.0 | |
86 | */ | |
87 | private URI identity; | |
88 | ||
89 | /** | |
90 | * The name of this part. | |
91 | * <p>Only used if the mix-in interface {@link Nameable} is implemented.</p> | |
92 | * @see #hasPartName() | |
93 | * @since 0.1.0 | |
94 | */ | |
95 | private String name; | |
96 | ||
97 | //---- Constructors | |
98 | ||
99 | /** | |
100 | * Creates a new, uninitialized basic part. | |
101 | * @since 0.1.0 | |
102 | */ | |
103 | protected BasicPart () { | |
104 | 9714 | super(); |
105 | 9714 | } |
106 | ||
107 | //---- Methods | |
108 | ||
109 | /** | |
110 | * Convenience method to reports a problem using the specified reporter. | |
111 | * <p>This method guarantees that the reporter is only called if it is not {@code null}.</p> | |
112 | * @param reporter the problem reporter to use, may be {@code null}. | |
113 | * @param message the problem to report. | |
114 | * @param param additional details about the type of violation. | |
115 | * @since 0.1.0 | |
116 | */ | |
117 | protected final void reportProblem ( | |
118 | PartProblemReporter reporter, ProblemMessage message, Object... param | |
119 | ) { | |
120 | 51 | if (reporter != null) { |
121 | 0 | reporter.reportProblem(this, message, param); |
122 | } | |
123 | 51 | } |
124 | ||
125 | /** | |
126 | * Convenience method to append the details of an extended part to a string builder. | |
127 | * <p>The convenience method takes care of the correct formatting.</p> | |
128 | * @param sb the string builder to append the details to. | |
129 | * @param type the type of details to add. | |
130 | * @param details the details to add. | |
131 | * @return the string builder specified to allow call chaining. | |
132 | * @since 0.1.0 | |
133 | */ | |
134 | protected final StringBuilder appendDetails (StringBuilder sb, String type, Object details) { | |
135 | 0 | return sb.append(", ").append(type).append("=").append(details); |
136 | } | |
137 | ||
138 | /** | |
139 | * Compares another part to this part. | |
140 | * @note The part supplied is guaranteed to be {@link #comparablePart(BasicPart) comparable} | |
141 | * to this part (i.e. it is safe to cast the object without any further checks if the | |
142 | * implementation of {@link #comparablePart(BasicPart)} makes the instanceof check). | |
143 | * @param part the part to compare to this part. | |
144 | * @return {@code true} if the part supplied is considered equivalent to this part. | |
145 | * @see BasicUtilities#equalObjects(Object, Object) | |
146 | * @see BasicPart | |
147 | * @since 0.1.0 | |
148 | */ | |
149 | protected boolean equalPart (BasicPart part) { | |
150 | 325 | return true; |
151 | } | |
152 | ||
153 | /** | |
154 | * Check if the part specified is comparable to this part. | |
155 | * @note Extensions should perform an {@code instanceof} check with the implementation class. | |
156 | * This check is performed in {@link #equals(Object)} before calling | |
157 | * {@link #equalPart(BasicPart)} to guarantee symmetry and transitivity on both objects. | |
158 | * @param part the part to check. | |
159 | * @return {@code true} if the part supplied is comparable to this part. | |
160 | * @see BasicPart | |
161 | * @since 0.1.0 | |
162 | */ | |
163 | protected abstract boolean comparablePart (BasicPart part); | |
164 | ||
165 | /** | |
166 | * Returns the hash code of the extended part state. | |
167 | * @note Extensions should calculate a combined hash code for all their extended state, | |
168 | * use {@link BasicUtilities#multiHashCode(int...)} to get a suitable hash code distribution. | |
169 | * @return the hash code of the extended part state. | |
170 | * @see BasicPart | |
171 | * @see BasicUtilities#multiHashCode(int...) | |
172 | * @since 0.1.0 | |
173 | */ | |
174 | protected int partHashCode () { | |
175 | 1101 | return 0; |
176 | } | |
177 | ||
178 | /** | |
179 | * Checks if a child part is not null. | |
180 | * @param part child part to check. | |
181 | * @return if all conditions are ok | |
182 | * @since 0.1.0 | |
183 | */ | |
184 | protected boolean isChildNotNull ( | |
185 | PartProblemReporter reporter, | |
186 | Part part) | |
187 | { | |
188 | boolean valid; | |
189 | ||
190 | // check if part is null | |
191 | 2 | valid = part != null; |
192 | 2 | if (!valid) { |
193 | 1 | reportProblem(reporter, ProblemMessage.nullChild); |
194 | } | |
195 | ||
196 | // return | |
197 | 2 | return valid; |
198 | } | |
199 | ||
200 | /** | |
201 | * Checks if a child part is null or valid. | |
202 | * @param part child part to check. | |
203 | * @return if all conditions are ok | |
204 | * @since 0.1.0 | |
205 | */ | |
206 | protected boolean isChildNullOrValid ( | |
207 | PartProblemReporter reporter, | |
208 | Part part) | |
209 | { | |
210 | 2 | return part == null || isChildValid(reporter, part); |
211 | } | |
212 | ||
213 | ||
214 | /** | |
215 | * Checks if a child part is set and valid. | |
216 | * @param part child part to check. | |
217 | * @return if all conditions are ok | |
218 | * @since 0.1.0 | |
219 | */ | |
220 | protected boolean isChildValid ( | |
221 | PartProblemReporter reporter, | |
222 | Part part) | |
223 | { | |
224 | boolean valid; | |
225 | ||
226 | // init | |
227 | 32 | valid = true; |
228 | ||
229 | // check if part is not null | |
230 | 32 | if (part == null) { |
231 | 4 | valid = false; |
232 | 4 | reportProblem(reporter, ProblemMessage.nullChild); |
233 | } | |
234 | ||
235 | // check if all part is valid | |
236 | 32 | if (part != null) { |
237 | 28 | if (!part.isValid(reporter)) { |
238 | 0 | valid = false; |
239 | 0 | reportProblem(reporter, ProblemMessage.invalidChild); |
240 | } | |
241 | } | |
242 | ||
243 | // return | |
244 | 32 | return valid; |
245 | } | |
246 | ||
247 | /** | |
248 | * Checks if a collection is set, elements are set and valid and not empty. | |
249 | * @param reporter reporter to use. | |
250 | * @param collection the collection to check. | |
251 | * @param checkNotEmpty check if collection is empty. | |
252 | * @param checkValid check if collection's elements are valid. | |
253 | * @param checkNameUniqueness check if collection's elements are name-unique. | |
254 | * @param checkObjectUniqueness check if collection's elements are object-unique. | |
255 | * @param checkIdentityUniqueness check if collectrion's elements are identity-unique. | |
256 | * @return <code>true</code> if all conditions are ok. | |
257 | * @since 0.3.0 | |
258 | */ | |
259 | protected boolean isChildCollectionValid ( | |
260 | PartProblemReporter reporter, | |
261 | Iterable<? extends Part> collection, | |
262 | boolean checkNotEmpty, | |
263 | boolean checkValid, | |
264 | boolean checkNameUniqueness, | |
265 | boolean checkObjectUniqueness, | |
266 | boolean checkIdentityUniqueness) | |
267 | { | |
268 | boolean valid; | |
269 | ||
270 | // init | |
271 | 45 | valid = true; |
272 | ||
273 | // check if collection is not null | |
274 | 45 | if (collection == null) { |
275 | 0 | valid = false; |
276 | 0 | reportProblem(reporter, ProblemMessage.nullChildCollection); |
277 | } | |
278 | ||
279 | // check if all elements are set | |
280 | 45 | if (collection != null) { |
281 | 45 | for (Part part : collection) { |
282 | 36 | if (part == null) { |
283 | 0 | valid = false; |
284 | 0 | reportProblem(reporter, ProblemMessage.collectionWithNullElements); |
285 | 0 | break; |
286 | } | |
287 | } | |
288 | } | |
289 | ||
290 | // check if all elements are valid | |
291 | 45 | if (checkValid) { |
292 | 41 | if (collection != null) { |
293 | 41 | for (Part part : collection) { |
294 | 34 | if (part != null && !part.isValid(reporter)) { |
295 | 2 | valid = false; |
296 | 2 | reportProblem(reporter, ProblemMessage.invalidChildCollection); |
297 | 2 | break; |
298 | } | |
299 | } | |
300 | } | |
301 | } | |
302 | ||
303 | // checkNotEmpty | |
304 | 45 | if (checkNotEmpty) { |
305 | 35 | if (collection != null && !collection.iterator().hasNext()) { |
306 | 9 | valid = false; |
307 | 9 | reportProblem(reporter, ProblemMessage.emptyChildCollection); |
308 | } | |
309 | } | |
310 | ||
311 | // checkNameUniqueness | |
312 | 45 | if (checkNameUniqueness) { |
313 | 10 | if (collection != null) { |
314 | 10 | Map<String, Part> map = new HashMap<String, Part>(); |
315 | 10 | for (Part part : collection) { |
316 | 8 | if (part != null && part instanceof Nameable) { |
317 | 8 | String partName = ((Nameable) part).getName(); |
318 | 8 | if (map.get(partName) != null) { |
319 | 1 | valid = false; |
320 | 1 | reportProblem( |
321 | reporter, ProblemMessage.collectionWithDuplicateName); | |
322 | 1 | break; |
323 | } | |
324 | 7 | map.put(partName, part); |
325 | 7 | } |
326 | } | |
327 | } | |
328 | } | |
329 | ||
330 | // checkIdentityUniqueness | |
331 | 45 | if (checkIdentityUniqueness) { |
332 | 4 | if (collection != null) { |
333 | 4 | Map<URI, Part> map = new HashMap<URI, Part>(); |
334 | 4 | for (Part part : collection) { |
335 | 4 | if (part != null && part instanceof Identifiable) { |
336 | 4 | URI partIdentity = ((Identifiable) part).getIdentity(); |
337 | 4 | if (map.get(partIdentity) != null) { |
338 | 2 | valid = false; |
339 | 2 | reportProblem( |
340 | reporter, ProblemMessage.collectionWithDuplicateIdentity); | |
341 | 2 | break; |
342 | } | |
343 | 2 | map.put(partIdentity, part); |
344 | 2 | } |
345 | } | |
346 | } | |
347 | } | |
348 | ||
349 | // checkObjectUniqueness | |
350 | 45 | if (checkObjectUniqueness) { |
351 | 32 | if (collection != null) { |
352 | 32 | Map<Object, Part> map = new HashMap<Object, Part>(); |
353 | 32 | for (Part part : collection) { |
354 | 21 | if (map.containsKey(part)) { |
355 | 2 | valid = false; |
356 | 2 | reportProblem( |
357 | reporter, ProblemMessage.collectionWithDuplicateObject); | |
358 | 2 | break; |
359 | } | |
360 | 19 | map.put(part, part); |
361 | } | |
362 | } | |
363 | } | |
364 | ||
365 | // return | |
366 | 45 | return valid; |
367 | } | |
368 | ||
369 | /** | |
370 | * Checks if the extended state of this part is valid. | |
371 | * @note Extensions should check their extended part state. | |
372 | * Use {@link #reportProblem} to avoid having to deal with {@code null} reporters. | |
373 | * @param reporter the problem reporter to use, may be {@code null}. | |
374 | * @see BasicPart | |
375 | * @see Part#isValid(PartProblemReporter) | |
376 | * @see #reportProblem | |
377 | * @since 0.1.0 | |
378 | */ | |
379 | public boolean isPartValid (PartProblemReporter reporter) { | |
380 | 48 | return true; |
381 | } | |
382 | ||
383 | /** | |
384 | * Appends the details of this extended part for the string representation of this part. | |
385 | * @note Extensions can add their details using the convenience method | |
386 | * {@link #appendDetails(StringBuilder, String, Object)} to guarantee proper formatting. | |
387 | * @param sb the string builder to add the details to. | |
388 | * @see BasicPart | |
389 | * @see #appendDetails(StringBuilder, String, Object) | |
390 | * @since 0.1.0 | |
391 | */ | |
392 | protected void appendPartDetails (StringBuilder sb) { | |
393 | // The default implementation does not add further part details. | |
394 | 0 | } |
395 | ||
396 | /** | |
397 | * Returns the string representation of this parts serial number if it has any. | |
398 | * <p>The default implementation simply returns {@code null}.</p> | |
399 | * @return the string representation of this parts serial number if it has any. | |
400 | * @since 0.1.0 | |
401 | */ | |
402 | protected String getSerialNumberString () { | |
403 | 0 | return null; |
404 | } | |
405 | ||
406 | /** | |
407 | * Notifies a change of the identity property. | |
408 | * <p>The default implementation does nothing.</p> | |
409 | * @param oldIdentity the old identity. | |
410 | * @param newIdentity the new identity. | |
411 | * @since 0.1.0 | |
412 | */ | |
413 | protected void notifyIdentityChange (URI oldIdentity, URI newIdentity) { | |
414 | // Nop. | |
415 | 34 | } |
416 | ||
417 | /** | |
418 | * Notifies a change of the name property. | |
419 | * <p>The default implementation does nothing.</p> | |
420 | * @param oldName the old name. | |
421 | * @param newName the new name. | |
422 | * @since 0.1.0 | |
423 | */ | |
424 | protected void notifyNameChange (String oldName, String newName) { | |
425 | // Nop. | |
426 | 0 | } |
427 | ||
428 | /** | |
429 | * Returns the simple implementation class name of this class. | |
430 | * <p>If your implementation represents an interface implementation return the interface | |
431 | * simple class name here. In short: return the instance you are checking against in | |
432 | * {@link #comparablePart(BasicPart)}.</p> | |
433 | * @return the simple implementation class name of this class. | |
434 | * @since 0.1.0 | |
435 | */ | |
436 | protected String getSimpleClassName () { | |
437 | 0 | return getClass().getSimpleName(); |
438 | } | |
439 | ||
440 | //---- PartIdentity | |
441 | ||
442 | /** | |
443 | * Check if the implementation of this part supports a {@link Identifiable}. | |
444 | * @return {@code true} if this part is a {@link Identifiable}. | |
445 | * @since 0.1.0 | |
446 | */ | |
447 | protected final boolean hasPartIdentity () { | |
448 | 12915 | return this instanceof Identifiable; |
449 | } | |
450 | ||
451 | /** | |
452 | * Check if the identity of this part and the part specified are equivalent. | |
453 | * <p>Makes sure that both objects implement the {@link Identifiable} mix-in interface | |
454 | * and then compares the identities. Also returns {@code true} if both objects do not | |
455 | * support the {@link Identifiable} interface.</p> | |
456 | * @param part the part to compare this identity to, must not be {@code null}. | |
457 | * @since 0.1.0 | |
458 | */ | |
459 | private final boolean equalIdentity (BasicPart part) { | |
460 | 5080 | if (hasPartIdentity()) { |
461 | 338 | return part.hasPartIdentity() && equalObjects(getIdentity(), part.getIdentity()); |
462 | } | |
463 | 4742 | return !part.hasPartIdentity(); |
464 | } | |
465 | ||
466 | /** | |
467 | * Returns the identity hash code of this part. | |
468 | * <p>If this part {@link #hasPartIdentity()} then the hash code of the identity is returned. | |
469 | * if it does not support the {@link Identifiable} mix-in interface {@code 0} is returned.</p> | |
470 | * @return the identity hash code of this part or {@code 0}. | |
471 | * @since 0.1.0 | |
472 | */ | |
473 | private final int identityHashCode () { | |
474 | 1438 | if (hasPartIdentity()) { |
475 | 1098 | return getIdentity() == null ? 0 : getIdentity().hashCode(); |
476 | } | |
477 | 340 | return 0; |
478 | } | |
479 | ||
480 | /** | |
481 | * Checks if the identity portion of this part is valid. | |
482 | * <p>The identity portion is only checked if this part {@link #hasPartIdentity()}.</p> | |
483 | * @return {@code true} if the identity portion of this part is valid or this part | |
484 | * does not implement the mix-in interface {@link Identifiable}. | |
485 | * @since 0.1.0 | |
486 | */ | |
487 | public final boolean isIdentityValid (PartProblemReporter reporter) { | |
488 | 84 | if (hasPartIdentity()) { |
489 | 36 | if (getIdentity() == null) { |
490 | 3 | reportProblem(reporter, ProblemMessage.nullIdentity); |
491 | 3 | return false; |
492 | } | |
493 | 33 | if (EMPTY_URI.equals(getIdentity())) { |
494 | 0 | reportProblem(reporter, ProblemMessage.emptyIdentity); |
495 | 0 | return false; |
496 | } | |
497 | } | |
498 | 81 | return true; |
499 | } | |
500 | ||
501 | /** | |
502 | * Appends the identity details of this part to the string builder specified. | |
503 | * <p>The identity details are only added if this part {@link #hasPartIdentity()}.</p> | |
504 | * @param sb the string builder to append to. | |
505 | * @since 0.1.0 | |
506 | */ | |
507 | private void appendIdentityDetails (StringBuilder sb) { | |
508 | 0 | if (hasPartIdentity()) { |
509 | 0 | appendDetails(sb, "identity", getIdentity()); |
510 | } | |
511 | 0 | } |
512 | ||
513 | /** | |
514 | * Returns the URI that serves as identity of this part. | |
515 | * @return the identity URI of this part. | |
516 | * @since 0.1.0 | |
517 | */ | |
518 | public final URI getIdentity () { | |
519 | 3274 | return this.identity; |
520 | } | |
521 | ||
522 | /** | |
523 | * Sets the URI that serves as identity of this part. | |
524 | * @param identity the new identity URI of this part to set. | |
525 | * @since 0.1.0 | |
526 | */ | |
527 | public final void setIdentity (URI identity) { | |
528 | 1233 | final URI oldIdentity = this.identity; |
529 | 1233 | this.identity = identity; |
530 | 1233 | if (hasPartIdentity()) { |
531 | 1233 | notifyIdentityChange(oldIdentity, identity); |
532 | } | |
533 | 1233 | } |
534 | ||
535 | //---- PartName | |
536 | ||
537 | /** | |
538 | * Check if the implementation of this part supports a {@link Nameable}. | |
539 | * @return {@code true} if this part is a {@link Nameable}. | |
540 | * @since 0.1.0 | |
541 | */ | |
542 | protected final boolean hasPartName () { | |
543 | 12294 | return this instanceof Nameable; |
544 | } | |
545 | ||
546 | /** | |
547 | * Check if the name of this part and the part specified are equivalent. | |
548 | * <p>Makes sure that both objects implement the {@link Nameable} mix-in interface | |
549 | * and then compares the name. Also returns {@code true} if both objects do not | |
550 | * support the {@link Nameable} interface.</p> | |
551 | * @param part the part to compare this name to, must not be {@code null}. | |
552 | * @since 0.1.0 | |
553 | */ | |
554 | private final boolean equalName (BasicPart part) { | |
555 | 5067 | if (hasPartName()) { |
556 | 176 | return part.hasPartName() && equalObjects(getName(), part.getName()); |
557 | } | |
558 | 4891 | return !part.hasPartName(); |
559 | } | |
560 | ||
561 | /** | |
562 | * Returns the name hash code of this part. | |
563 | * <p>If this part {@link #hasPartName()} then the hash code of the name is returned. | |
564 | * if it does not support the {@link Nameable} mix-in interface {@code 0} is returned.</p> | |
565 | * @return the name hash code of this part or {@code 0}. | |
566 | * @since 0.1.0 | |
567 | */ | |
568 | private final int nameHashCode () { | |
569 | 1438 | if (hasPartName()) { |
570 | 106 | return getName() == null ? 0 : getName().hashCode(); |
571 | } | |
572 | 1332 | return 0; |
573 | } | |
574 | ||
575 | /** | |
576 | * Checks if the name portion of this part is valid. | |
577 | * <p>The name portion is only checked if this part {@link #hasPartName()}.</p> | |
578 | * @return {@code true} if the name portion of this part is valid or this part | |
579 | * does not implement the mix-in interface {@link Nameable}. | |
580 | * @since 0.1.0 | |
581 | */ | |
582 | public final boolean isNameValid (PartProblemReporter reporter) { | |
583 | 84 | if (hasPartName()) { |
584 | 13 | if (getName() == null) { |
585 | 1 | reportProblem(reporter, ProblemMessage.nameMissing); |
586 | 1 | return false; |
587 | } | |
588 | 12 | if (getName().length() == 0) { |
589 | 1 | reportProblem(reporter, ProblemMessage.nameEmpty); |
590 | 1 | return false; |
591 | } | |
592 | } | |
593 | 82 | return true; |
594 | } | |
595 | ||
596 | /** | |
597 | * Appends the name details of this part to the string builder specified. | |
598 | * <p>The name details are only added if this part {@link #hasPartName()}.</p> | |
599 | * @param sb the string builder to append to. | |
600 | * @since 0.1.0 | |
601 | */ | |
602 | private void appendNameDetails (StringBuilder sb) { | |
603 | 0 | if (hasPartName()) { |
604 | 0 | appendDetails(sb, "name", getName()); |
605 | } | |
606 | 0 | } |
607 | ||
608 | /** | |
609 | * Returns the name of this part. | |
610 | * @return the name of this part. | |
611 | * @since 0.1.0 | |
612 | */ | |
613 | public final String getName () { | |
614 | 1111 | return this.name; |
615 | } | |
616 | ||
617 | /** | |
618 | * Sets the name of this part. | |
619 | * @param name the new name of this part. | |
620 | * @since 0.1.0 | |
621 | */ | |
622 | public final void setName (String name) { | |
623 | 638 | final String oldName = this.name; |
624 | 638 | this.name = name; |
625 | 638 | if (hasPartName()) { |
626 | 638 | notifyNameChange(oldName, name); |
627 | } | |
628 | 638 | } |
629 | ||
630 | //---- Part | |
631 | ||
632 | /** | |
633 | * @since 0.1.0 | |
634 | */ | |
635 | public final boolean isValid (PartProblemReporter reporter) { | |
636 | 84 | boolean valid = true; |
637 | ||
638 | 84 | if (!isIdentityValid(reporter)) { |
639 | 3 | valid = false; |
640 | } | |
641 | ||
642 | 84 | if (!isNameValid(reporter)) { |
643 | 2 | valid = false; |
644 | } | |
645 | ||
646 | 84 | if (!isPartValid(reporter)) { |
647 | 17 | valid = false; |
648 | } | |
649 | ||
650 | 84 | return valid; |
651 | } | |
652 | ||
653 | //---- Object | |
654 | ||
655 | /** | |
656 | * @since 0.1.0 | |
657 | */ | |
658 | @Override | |
659 | public final boolean equals (Object obj) { | |
660 | 5167 | if (obj == null) { |
661 | 0 | return false; |
662 | } | |
663 | 5167 | if (obj == this) { |
664 | 87 | return true; |
665 | } | |
666 | 5080 | if (obj instanceof BasicPart) { |
667 | 5080 | final BasicPart part = (BasicPart) obj; |
668 | // Check equality of mix-in interfaces. | |
669 | 5080 | if (!equalIdentity(part) || !equalName(part)) { |
670 | 25 | return false; |
671 | } | |
672 | // Check symmetry and transitivity before calling equalsPart. | |
673 | 5055 | if (comparablePart(part) && part.comparablePart(this)) { |
674 | 5055 | return equalPart(part); |
675 | } | |
676 | } | |
677 | 0 | return false; |
678 | } | |
679 | ||
680 | /** | |
681 | * @since 0.1.0 | |
682 | */ | |
683 | @Override | |
684 | public final int hashCode () { | |
685 | 1438 | return multiHashCode(identityHashCode(), nameHashCode(), partHashCode()); |
686 | } | |
687 | ||
688 | /** | |
689 | * @since 0.1.0 | |
690 | */ | |
691 | @Override | |
692 | public final String toString () { | |
693 | 0 | final StringBuilder sb = new StringBuilder(getSimpleClassName()); |
694 | 0 | final String serial = getSerialNumberString(); |
695 | 0 | if (serial != null) { |
696 | 0 | sb.append(" [serial=").append(serial); |
697 | } else { | |
698 | 0 | sb.append(" ["); |
699 | } | |
700 | 0 | appendIdentityDetails(sb); |
701 | 0 | appendNameDetails(sb); |
702 | 0 | appendPartDetails(sb); |
703 | 0 | return sb.append("]").toString(); |
704 | } | |
705 | ||
706 | } |