Added clone() to all TaxonNameBase classes (including some descriptive and common...
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / name / HomotypicalGroup.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.cdm.model.name;
11
12 import java.util.ArrayList;
13 import java.util.Collections;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Set;
17
18 import javax.persistence.Entity;
19 import javax.persistence.FetchType;
20 import javax.persistence.OneToMany;
21 import javax.persistence.Transient;
22 import javax.xml.bind.annotation.XmlAccessType;
23 import javax.xml.bind.annotation.XmlAccessorType;
24 import javax.xml.bind.annotation.XmlElement;
25 import javax.xml.bind.annotation.XmlElementWrapper;
26 import javax.xml.bind.annotation.XmlIDREF;
27 import javax.xml.bind.annotation.XmlSchemaType;
28 import javax.xml.bind.annotation.XmlType;
29
30 import org.apache.log4j.Logger;
31 import org.hibernate.envers.Audited;
32
33 import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
34 import eu.etaxonomy.cdm.model.reference.Reference;
35 import eu.etaxonomy.cdm.model.taxon.Synonym;
36 import eu.etaxonomy.cdm.model.taxon.TaxonComparator;
37
38
39 /**
40 * The homotypical group class represents a set of {@link TaxonNameBase taxon names} associated
41 * on the base of their typifications. Since it can be asserted that two taxon
42 * names are typified by the same type without mentioning the type itself, even
43 * taxon names without explicit {@link TypeDesignationBase type designation} can belong
44 * to an homotypical group.<BR>
45 * Taxon names belonging to an homotypical group and the taxon names or
46 * {@link eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase specimens} used as types for their
47 * {@link TypeDesignationBase type designations} have the following properties: <ul>
48 * <li> A taxon name belongs exactly to one homotypical group
49 * <li> A type specimen or a type name can be used as a type only for taxon
50 * names belonging to the same homotypical group<BR>
51 * - therefore an homotypical group circumscribes a set of types<BR>
52 * - each taxon name shares a subset of these types<BR>
53 * - each type is used by a subset of these taxon names
54 * within the homotypical group
55 * <li> Names that share at least one common type must belong to the same
56 * homotypical group
57 * <li> Names that share the same basionym or replaced synonym must belong to
58 * the same homotypical group
59 * </ul>
60 *
61 * @see TypeDesignationBase
62 * @see NameTypeDesignation
63 * @see SpecimenTypeDesignation
64 * @author m.doering
65 * @version 1.0
66 * @created 08-Nov-2007
67 */
68 @XmlAccessorType(XmlAccessType.FIELD)
69 @XmlType(name = "HomotypicalGroup", propOrder = {
70 "typifiedNames"
71 })
72 @Entity
73 @Audited
74 public class HomotypicalGroup extends AnnotatableEntity {
75 private static final Logger logger = Logger.getLogger(HomotypicalGroup.class);
76
77 @XmlElementWrapper(name = "TypifiedNames")
78 @XmlElement(name = "TypifiedName")
79 @XmlIDREF
80 @XmlSchemaType(name = "IDREF")
81 @OneToMany(mappedBy="homotypicalGroup", fetch=FetchType.LAZY)
82 protected Set<TaxonNameBase> typifiedNames = new HashSet<TaxonNameBase>();
83
84 // ******************** static methods **************************************/
85 /**
86 * Creates a new homotypical group instance with an empty set of typified
87 * {@link TaxonNameBase taxon names}.
88 *
89 * @see #HomotypicalGroup()
90 */
91 public static HomotypicalGroup NewInstance(){
92 return new HomotypicalGroup();
93 }
94
95
96 //********************** CONSTRUCTOR ********************************************/
97
98 /**
99 * Class constructor: creates a new homotypical group instance with an
100 * empty set of typified {@link TaxonNameBase taxon names}.
101 */
102 public HomotypicalGroup() {
103 super();
104 }
105
106 // ********************** GETTER/SETTER/ADDER/REMOVER ********************************/
107
108 /**
109 * Returns the set of {@link TaxonNameBase taxon names} that belong to <i>this</i> homotypical group.
110 *
111 * @see #getSpecimenTypeDesignations()
112 */
113 public Set<TaxonNameBase> getTypifiedNames() {
114 return typifiedNames;
115 }
116
117 /**
118 * Adds a new {@link TaxonNameBase taxon name} to the set of taxon names that belong
119 * to <i>this</i> homotypical group.
120 *
121 * @param typifiedName the taxon name to be added to <i>this</i> group
122 * @see #getTypifiedNames()
123 * @see #removeTypifiedName(TaxonNameBase)
124 */
125 public void addTypifiedName(TaxonNameBase typifiedName) {
126 if (typifiedName != null){
127 typifiedName.setHomotypicalGroup(this);
128 typifiedNames.add(typifiedName);
129 }
130 }
131 /**
132 * Removes one element from the set of {@link TaxonNameBase taxon names}
133 * that belong to <i>this</i> homotypical group.
134 *
135 * @param taxonBase the taxon name which should be removed from the corresponding set
136 * @see #addTypifiedName(TaxonNameBase)
137 */
138 public void removeTypifiedName(TaxonNameBase typifiedName) {
139 typifiedName.setHomotypicalGroup(HomotypicalGroup.NewInstance());
140 typifiedNames.remove(typifiedName);
141 }
142
143 /**
144 * Merges the typified {@link TaxonNameBase taxon names} from one homotypical group into
145 * the set of typified taxon names of <i>this</i> homotypical group.
146 *
147 * @param homotypicalGroupToMerge the homotypical group the typified names of which
148 * are to be transferred to <i>this</i> homotypical group
149 */
150 public void merge(HomotypicalGroup homotypicalGroupToMerge){
151 if (homotypicalGroupToMerge != null){
152 Set<TaxonNameBase> typifiedNames = new HashSet<TaxonNameBase>();
153 typifiedNames.addAll(homotypicalGroupToMerge.getTypifiedNames());
154 for (TaxonNameBase typifiedName: typifiedNames){
155 this.addTypifiedName(typifiedName);
156 }
157 }
158 }
159
160
161 /**
162 * Returns the set of {@link SpecimenTypeDesignation specimen type designations} that
163 * typify the {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group
164 * including the status of these designations.
165 *
166 * @see #getTypifiedNames()
167 * @see #getNameTypeDesignations()
168 * @see #getTypeDesignations()
169 * @see TaxonNameBase#getSpecimenTypeDesignations()
170 */
171 @Transient
172 public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations(){
173 Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
174 for (TaxonNameBase taxonName : typifiedNames){
175 result.addAll(taxonName.getSpecimenTypeDesignations());
176 }
177 return result;
178 }
179
180 /**
181 * Returns the set of {@link NameTypeDesignation name type designations} that
182 * typify the {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group
183 * including the status of these designations.
184 *
185 * @see #getTypifiedNames()
186 * @see #getSpecimenTypeDesignations()
187 * @see #getTypeDesignations()
188 * @see TaxonNameBase#getNameTypeDesignations()
189 */
190 @Transient
191 public Set<NameTypeDesignation> getNameTypeDesignations(){
192 Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
193 for (TaxonNameBase taxonName : typifiedNames){
194 result.addAll(taxonName.getNameTypeDesignations());
195 }
196 return result;
197 }
198
199
200 /**
201 * Returns the set of all {@link TypeDesignationBase type designations} that
202 * typify the {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group
203 * (this includes either {@link NameTypeDesignation name type designations} or
204 * {@link SpecimenTypeDesignation specimen type designations}).
205 *
206 * @see #getTypifiedNames()
207 * @see #getNameTypeDesignations()
208 * @see #getSpecimenTypeDesignations()
209 * @see TaxonNameBase#getTypeDesignations()
210 */
211 @Transient
212 public Set<TypeDesignationBase> getTypeDesignations(){
213 Set<TypeDesignationBase> result = new HashSet<TypeDesignationBase>();
214 for (TaxonNameBase taxonName : typifiedNames){
215 result.addAll(taxonName.getTypeDesignations());
216 }
217 return result;
218 }
219
220 // /**
221 // * Returns the set of {@link SpecimenTypeDesignation specimen type designations} that
222 // * typify <i>this</i> homotypical group including the status of these designations.
223 // *
224 // * @see #getTypifiedNames()
225 // */
226 // @OneToMany
227 // @Cascade({CascadeType.SAVE_UPDATE})
228 // public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
229 // return specimenTypeDesignations;
230 // }
231 // /**
232 // * @see #getSpecimenTypeDesignations()
233 // */
234 // protected void setSpecimenTypeDesignations(Set<SpecimenTypeDesignation> specimenTypeDesignations) {
235 // this.specimenTypeDesignations = specimenTypeDesignations;
236 // }
237 // /**
238 // * Adds a new {@link SpecimenTypeDesignation specimen type designation} to the set
239 // * of specimen type designations assigned to <i>this</i> homotypical group and eventually
240 // * (with a boolean parameter) also to the corresponding set of each of the
241 // * {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group.
242 // *
243 // * @param specimenTypeDesignation the specimen type designation to be added
244 // * @param addToAllNames the boolean flag indicating whether the addition will also
245 // * carried out for each taxon name
246 // *
247 // * @see TaxonNameBase#getSpecimenTypeDesignations()
248 // * @see SpecimenTypeDesignation
249 // */
250 // public void addSpecimenTypeDesignation(SpecimenTypeDesignation specimenTypeDesignation, boolean addToAllNames) {
251 // if (specimenTypeDesignation != null){
252 // specimenTypeDesignation.setHomotypicalGroup(this);
253 // specimenTypeDesignations.add(specimenTypeDesignation);
254 // }
255 // if (addToAllNames){
256 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
257 // taxonNameBase.addSpecimenTypeDesignation(specimenTypeDesignation);
258 // }
259 // }
260 // }
261 // /**
262 // * Removes one element from the set of {@link SpecimenTypeDesignation specimen type designations} assigned to the
263 // * {@link HomotypicalGroup homotypical group} to which this {@link TaxonNameBase taxon name} belongs.
264 // * The same element will be removed from the corresponding set of each of
265 // * the taxon names belonging to <i>this</i> homotypical group. Furthermore the
266 // * homotypical group attribute of the specimen type designation will be
267 // * nullified.
268 // *
269 // * @param specimenTypeDesignation the specimen type designation which should be deleted
270 // * @see #getSpecimenTypeDesignations()
271 // * @see #addSpecimenTypeDesignation(SpecimenTypeDesignation, boolean)
272 // * @see TaxonNameBase#removeSpecimenTypeDesignation(SpecimenTypeDesignation)
273 // * @see SpecimenTypeDesignation#getHomotypicalGroup()
274 // */
275 // public void removeSpecimenTypeDesignation(SpecimenTypeDesignation specimenTypeDesignation) {
276 // if (specimenTypeDesignation != null){
277 // specimenTypeDesignation.setHomotypicalGroup(null);
278 // specimenTypeDesignations.remove(specimenTypeDesignation);
279 // }
280 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
281 // taxonNameBase.removeSpecimenTypeDesignation(specimenTypeDesignation);
282 // }
283 // }
284
285
286 // /**
287 // * Returns the set of {@link NameTypeDesignation name type designations} that
288 // * typify <i>this</i> homotypical group including the status of these designations.
289 // *
290 // * @see #getTypifiedNames()
291 // */
292 // @OneToMany
293 // @Cascade({CascadeType.SAVE_UPDATE})
294 // public Set<NameTypeDesignation> getNameTypeDesignations() {
295 // return nameTypeDesignations;
296 // }
297 // /**
298 // * @see #getNameTypeDesignations()
299 // */
300 // protected void setNameTypeDesignations(Set<NameTypeDesignation> nameTypeDesignations) {
301 // this.nameTypeDesignations = nameTypeDesignations;
302 // }
303 // /**
304 // * Adds a new {@link NameTypeDesignation name type designation} to the set
305 // * of name type designations assigned to <i>this</i> homotypical group and eventually
306 // * (with a boolean parameter) also to the corresponding set of each of the
307 // * {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group.
308 // *
309 // * @param nameTypeDesignation the name type designation to be added
310 // * @param addToAllNames the boolean flag indicating whether the addition will also
311 // * carried out for each taxon name
312 // *
313 // * @see TaxonNameBase#getNameTypeDesignations()
314 // * @see NameTypeDesignation
315 // */
316 // public void addNameTypeDesignation(NameTypeDesignation nameTypeDesignation, boolean addToAllNames) {
317 // if (nameTypeDesignation != null){
318 // nameTypeDesignation.setHomotypicalGroup(this);
319 // nameTypeDesignations.add(nameTypeDesignation);
320 // }
321 // if (addToAllNames){
322 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
323 // taxonNameBase.addNameTypeDesignation(nameTypeDesignation);
324 // }
325 // }
326 // }
327 // /**
328 // * Removes one element from the set of {@link NameTypeDesignation name type designations} assigned to the
329 // * {@link HomotypicalGroup homotypical group} to which this {@link TaxonNameBase taxon name} belongs.
330 // * The same element will be removed from the corresponding set of each of
331 // * the taxon names belonging to <i>this</i> homotypical group. Furthermore the
332 // * homotypical group attribute of the name type designation will be
333 // * nullified.
334 // *
335 // * @param nameTypeDesignation the name type designation which should be deleted
336 // * @see #getNameTypeDesignations()
337 // * @see #addNameTypeDesignation(NameTypeDesignation, boolean)
338 // * @see TaxonNameBase#removeNameTypeDesignation(NameTypeDesignation)
339 // * @see NameTypeDesignation#getHomotypicalGroup()
340 // */
341 // public void removeNameTypeDesignation(NameTypeDesignation nameTypeDesignation) {
342 // if (nameTypeDesignation != null){
343 // nameTypeDesignation.setHomotypicalGroup(null);
344 // nameTypeDesignations.remove(nameTypeDesignation);
345 // }
346 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
347 // taxonNameBase.removeNameTypeDesignation(nameTypeDesignation);
348 // }
349 // }
350
351
352 /**
353 * Retrieves the ordered list (depending on the date of publication) of
354 * {@link taxon.Synonym synonyms} (according to a given reference)
355 * the {@link TaxonNameBase taxon names} of which belong to <i>this</i> homotypical group.
356 * If other names are part of <i>this</i> group that are not considered synonyms
357 * according to the respective reference, then they will not be included in
358 * the result set.
359 *
360 * @param sec the reference whose treatment is to be considered
361 * @return the ordered list of synonyms
362 * @see TaxonNameBase#getSynonyms()
363 * @see TaxonNameBase#getTaxa()
364 * @see taxon.Synonym
365 */
366 public List<Synonym> getSynonymsInGroup(Reference sec){
367 List<Synonym> result = new ArrayList();
368 for (TaxonNameBase<?, ?>name : this.getTypifiedNames()){
369 for (Synonym synonym : name.getSynonyms()){
370 if ( (synonym.getSec() == null && sec == null) ||
371 synonym.getSec() != null && synonym.getSec().equals(sec)){
372 result.add(synonym);
373 }
374 }
375 }
376 Collections.sort(result, new TaxonComparator());
377 return result;
378 }
379
380
381 /**
382 * Creates a basionym relationship to all other names in this names homotypical
383 * group.
384 *
385 * @see HomotypicalGroup.setGroupBasionym(TaxonNameBase basionymName)
386 *
387 * @param basionymName
388 * @throws IllegalArgumentException if basionymName is not member in this homotypical group
389 */
390 public void setGroupBasionym(TaxonNameBase basionymName) throws IllegalArgumentException{
391 setGroupBasionym(basionymName, null, null, null);
392 }
393
394 public void setGroupBasionym(TaxonNameBase basionymName, Reference citation, String microCitation, String ruleConsidered)
395 throws IllegalArgumentException {
396 if (! typifiedNames.contains(basionymName)){
397 throw new IllegalArgumentException("Name to be set as basionym/original combination must be part of the homotypical group but is not");
398 }
399 if (typifiedNames.size() < 2){return;}
400 //
401 //Add new relations
402 for (TaxonNameBase name : typifiedNames) {
403 if (!name.equals(basionymName)) {
404 name.addRelationshipFromName(basionymName, NameRelationshipType.BASIONYM(), citation, microCitation, ruleConsidered);
405 }
406 }
407 }
408
409 /**
410 * Removes all basionym relationships between basionymName and any other name
411 * in its homotypic group
412 *
413 * @param basionymName
414 */
415 public static void removeGroupBasionym(TaxonNameBase basionymName) {
416 HomotypicalGroup homotypicalGroup = basionymName.getHomotypicalGroup();
417 Set<NameRelationship> relations = basionymName.getRelationsFromThisName();
418 Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
419
420 for (NameRelationship relation : relations) {
421
422 // If this is a basionym relation, and toName is in the homotypical group,
423 // remove the relationship.
424 if (relation.getType().isBasionymRelation() &&
425 relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
426 removeRelations.add(relation);
427 }
428 }
429
430 // Removing relations from a set through which we are iterating causes a
431 // ConcurrentModificationException. Therefore, we delete the targeted
432 // relations in a second step.
433 for (NameRelationship relation : removeRelations) {
434 basionymName.removeNameRelationship(relation);
435 }
436 }
437
438
439 /**
440 * Returns all taxon names in the homotypical group that do not have an 'is_basionym_for' (zool.: 'is_original_combination_for')
441 * or a replaced synonym relationship.
442 * @return
443 */
444 @Transient
445 public Set<TaxonNameBase> getUnrelatedNames(){
446 Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, true);
447 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
448 result.addAll(this.getTypifiedNames());
449 for (NameRelationship nameRelationship : set){
450 result.remove(nameRelationship.getFromName());
451 result.remove(nameRelationship.getToName());
452 }
453 return result;
454 }
455
456 /**
457 * Returns all taxon names in the homotypical group that are new combinations (have a basionym/original combination
458 * or a replaced synonym).
459 * @return
460 */
461 @Transient
462 public Set<TaxonNameBase> getNewCombinations(){
463 Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, true);
464 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
465 for (NameRelationship nameRelationship : set){
466 result.add(nameRelationship.getToName());
467 }
468 return result;
469 }
470
471
472
473 /**
474 * Returns all taxon names in the homotypical group that have an 'is_basionym_for' (zool.: 'is_original_combination_for')
475 * or a replaced synonym relationship.
476 * @return
477 */
478 @Transient
479 public Set<TaxonNameBase> getBasionymsOrReplacedSynonyms(){
480 Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, true);
481 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
482 for (NameRelationship nameRelationship : set){
483 result.add(nameRelationship.getFromName());
484 }
485 return result;
486 }
487
488 /**
489 * Returns all taxon names in the homotypical group that have a 'is_basionym_for' (zool.: 'is_original_combination_for') relationship.
490 * @return
491 */
492 @Transient
493 public Set<TaxonNameBase> getBasionyms(){
494 Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, false);
495 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
496 for (NameRelationship nameRelationship : set){
497 result.add(nameRelationship.getFromName());
498 }
499 return result;
500 }
501
502 /**
503 * Returns all taxon names in the homotypical group that have a 'is_replaced_synonym_for' relationship.
504 * @return
505 */
506 @Transient
507 public Set<TaxonNameBase> getReplacedSynonym(){
508 Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(false, true);
509 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
510 for (NameRelationship nameRelationship : set){
511 result.add(nameRelationship.getFromName());
512 }
513 return result;
514 }
515
516 /**
517 * Returns the name relationships that represent either a basionym (original combination) relationship or
518 * a replaced synonym relationship.
519 * @return
520 */
521 @Transient
522 public Set<NameRelationship> getBasionymAndReplacedSynonymRelations(){
523 return getBasionymOrReplacedSynonymRelations(true, true);
524 }
525
526 /**
527 * Computes all basionym and replaced synonym relationships between names in this group.
528 * If <code>doBasionym</code> is <code>false</code> basionym relationships are excluded.
529 * If <code>doReplacedSynonym</code> is <code>false</code> replaced synonym relationships are excluded.
530 * @param doBasionym
531 * @param doReplacedSynonym
532 * @return
533 */
534 @Transient
535 private Set<NameRelationship> getBasionymOrReplacedSynonymRelations(boolean doBasionym, boolean doReplacedSynonym){
536 Set<NameRelationship> result = new HashSet<NameRelationship>();
537 Set<TaxonNameBase> names = this.getTypifiedNames();
538 if (names.size() > 1){
539 for (TaxonNameBase name : names){
540 Set nameRels = name.getNameRelations();
541 //TODO make getNameRelations generic
542 for (Object obj : nameRels){
543 NameRelationship nameRel = (NameRelationship)obj;
544 NameRelationshipType type = nameRel.getType();
545 if ( type.isBasionymRelation() && doBasionym){
546 if (testRelatedNameInThisGroup(nameRel)){
547 result.add(nameRel);
548 }else{
549 logger.warn("Name has basionym relation to a name that is not in the same homotypical group");
550 }
551 }else if (type.isReplacedSynonymRelation() && doReplacedSynonym) {
552 if (testRelatedNameInThisGroup(nameRel)){
553 result.add(nameRel);
554 }else{
555 logger.warn("Name has replaced synonym relation to a name that is not in the same homotypical group");
556 }
557 }
558 }
559 }
560 }
561 return result;
562 }
563
564 private boolean testRelatedNameInThisGroup(NameRelationship nameRel){
565 TaxonNameBase toName = nameRel.getToName();
566 return (this.getTypifiedNames().contains(toName));
567 }
568
569 private boolean isBasionymOrRepSynRel(NameRelationshipType relType){
570 if (relType == null){
571 throw new IllegalArgumentException("NameRelationshipType should never be null");
572 }else if (relType.equals(NameRelationshipType.BASIONYM())) {
573 return true;
574 }else if (relType.equals(NameRelationshipType.REPLACED_SYNONYM())){
575 return true;
576 }else{
577 return false;
578 }
579 }
580 }