fix in taxonomic tree and some comments
[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.ReferenceBase;
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 static 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 /**
85 * Class constructor: creates a new homotypical group instance with an
86 * empty set of typified {@link TaxonNameBase taxon names}.
87 */
88 public HomotypicalGroup() {
89 super();
90 }
91
92 /**
93 * Creates a new homotypical group instance with an empty set of typified
94 * {@link TaxonNameBase taxon names}.
95 *
96 * @see #HomotypicalGroup()
97 */
98 public static HomotypicalGroup NewInstance(){
99 return new HomotypicalGroup();
100 }
101
102 /**
103 * Returns the set of {@link TaxonNameBase taxon names} that belong to <i>this</i> homotypical group.
104 *
105 * @see #getSpecimenTypeDesignations()
106 */
107 public Set<TaxonNameBase> getTypifiedNames() {
108 return typifiedNames;
109 }
110
111 /**
112 * Adds a new {@link TaxonNameBase taxon name} to the set of taxon names that belong
113 * to <i>this</i> homotypical group.
114 *
115 * @param typifiedName the taxon name to be added to <i>this</i> group
116 * @see #getTypifiedNames()
117 * @see #removeTypifiedName(TaxonNameBase)
118 */
119 public void addTypifiedName(TaxonNameBase typifiedName) {
120 if (typifiedName != null){
121 typifiedName.setHomotypicalGroup(this);
122 typifiedNames.add(typifiedName);
123 }
124 }
125 /**
126 * Removes one element from the set of {@link TaxonNameBase taxon names}
127 * that belong to <i>this</i> homotypical group.
128 *
129 * @param taxonBase the taxon name which should be removed from the corresponding set
130 * @see #addTypifiedName(TaxonNameBase)
131 */
132 public void removeTypifiedName(TaxonNameBase typifiedName) {
133 typifiedName.setHomotypicalGroup(null);
134 typifiedNames.remove(typifiedName);
135 }
136
137 /**
138 * Merges the typified {@link TaxonNameBase taxon names} from one homotypical group into
139 * the set of typified taxon names of <i>this</i> homotypical group.
140 *
141 * @param homotypicalGroupToMerge the homotypical group the typified names of which
142 * are to be transferred to <i>this</i> homotypical group
143 */
144 public void merge(HomotypicalGroup homotypicalGroupToMerge){
145 if (homotypicalGroupToMerge != null){
146 Set<TaxonNameBase> typifiedNames = new HashSet<TaxonNameBase>();
147 typifiedNames.addAll(homotypicalGroupToMerge.getTypifiedNames());
148 for (TaxonNameBase typifiedName: typifiedNames){
149 this.addTypifiedName(typifiedName);
150 }
151 }
152 }
153
154
155 /**
156 * Returns the set of {@link SpecimenTypeDesignation specimen type designations} that
157 * typify the {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group
158 * including the status of these designations.
159 *
160 * @see #getTypifiedNames()
161 * @see #getNameTypeDesignations()
162 * @see #getTypeDesignations()
163 * @see TaxonNameBase#getSpecimenTypeDesignations()
164 */
165 @Transient
166 public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations(){
167 Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
168 for (TaxonNameBase taxonName : typifiedNames){
169 result.addAll(taxonName.getSpecimenTypeDesignations());
170 }
171 return result;
172 }
173
174 /**
175 * Returns the set of {@link NameTypeDesignation name type designations} that
176 * typify the {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group
177 * including the status of these designations.
178 *
179 * @see #getTypifiedNames()
180 * @see #getSpecimenTypeDesignations()
181 * @see #getTypeDesignations()
182 * @see TaxonNameBase#getNameTypeDesignations()
183 */
184 @Transient
185 public Set<NameTypeDesignation> getNameTypeDesignations(){
186 Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
187 for (TaxonNameBase taxonName : typifiedNames){
188 result.addAll(taxonName.getNameTypeDesignations());
189 }
190 return result;
191 }
192
193
194 /**
195 * Returns the set of all {@link TypeDesignationBase type designations} that
196 * typify the {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group
197 * (this includes either {@link NameTypeDesignation name type designations} or
198 * {@link SpecimenTypeDesignation specimen type designations}).
199 *
200 * @see #getTypifiedNames()
201 * @see #getNameTypeDesignations()
202 * @see #getSpecimenTypeDesignations()
203 * @see TaxonNameBase#getTypeDesignations()
204 */
205 @Transient
206 public Set<TypeDesignationBase> getTypeDesignations(){
207 Set<TypeDesignationBase> result = new HashSet<TypeDesignationBase>();
208 for (TaxonNameBase taxonName : typifiedNames){
209 result.addAll(taxonName.getTypeDesignations());
210 }
211 return result;
212 }
213
214 // /**
215 // * Returns the set of {@link SpecimenTypeDesignation specimen type designations} that
216 // * typify <i>this</i> homotypical group including the status of these designations.
217 // *
218 // * @see #getTypifiedNames()
219 // */
220 // @OneToMany
221 // @Cascade({CascadeType.SAVE_UPDATE})
222 // public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
223 // return specimenTypeDesignations;
224 // }
225 // /**
226 // * @see #getSpecimenTypeDesignations()
227 // */
228 // protected void setSpecimenTypeDesignations(Set<SpecimenTypeDesignation> specimenTypeDesignations) {
229 // this.specimenTypeDesignations = specimenTypeDesignations;
230 // }
231 // /**
232 // * Adds a new {@link SpecimenTypeDesignation specimen type designation} to the set
233 // * of specimen type designations assigned to <i>this</i> homotypical group and eventually
234 // * (with a boolean parameter) also to the corresponding set of each of the
235 // * {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group.
236 // *
237 // * @param specimenTypeDesignation the specimen type designation to be added
238 // * @param addToAllNames the boolean flag indicating whether the addition will also
239 // * carried out for each taxon name
240 // *
241 // * @see TaxonNameBase#getSpecimenTypeDesignations()
242 // * @see SpecimenTypeDesignation
243 // */
244 // public void addSpecimenTypeDesignation(SpecimenTypeDesignation specimenTypeDesignation, boolean addToAllNames) {
245 // if (specimenTypeDesignation != null){
246 // specimenTypeDesignation.setHomotypicalGroup(this);
247 // specimenTypeDesignations.add(specimenTypeDesignation);
248 // }
249 // if (addToAllNames){
250 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
251 // taxonNameBase.addSpecimenTypeDesignation(specimenTypeDesignation);
252 // }
253 // }
254 // }
255 // /**
256 // * Removes one element from the set of {@link SpecimenTypeDesignation specimen type designations} assigned to the
257 // * {@link HomotypicalGroup homotypical group} to which this {@link TaxonNameBase taxon name} belongs.
258 // * The same element will be removed from the corresponding set of each of
259 // * the taxon names belonging to <i>this</i> homotypical group. Furthermore the
260 // * homotypical group attribute of the specimen type designation will be
261 // * nullified.
262 // *
263 // * @param specimenTypeDesignation the specimen type designation which should be deleted
264 // * @see #getSpecimenTypeDesignations()
265 // * @see #addSpecimenTypeDesignation(SpecimenTypeDesignation, boolean)
266 // * @see TaxonNameBase#removeSpecimenTypeDesignation(SpecimenTypeDesignation)
267 // * @see SpecimenTypeDesignation#getHomotypicalGroup()
268 // */
269 // public void removeSpecimenTypeDesignation(SpecimenTypeDesignation specimenTypeDesignation) {
270 // if (specimenTypeDesignation != null){
271 // specimenTypeDesignation.setHomotypicalGroup(null);
272 // specimenTypeDesignations.remove(specimenTypeDesignation);
273 // }
274 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
275 // taxonNameBase.removeSpecimenTypeDesignation(specimenTypeDesignation);
276 // }
277 // }
278
279
280 // /**
281 // * Returns the set of {@link NameTypeDesignation name type designations} that
282 // * typify <i>this</i> homotypical group including the status of these designations.
283 // *
284 // * @see #getTypifiedNames()
285 // */
286 // @OneToMany
287 // @Cascade({CascadeType.SAVE_UPDATE})
288 // public Set<NameTypeDesignation> getNameTypeDesignations() {
289 // return nameTypeDesignations;
290 // }
291 // /**
292 // * @see #getNameTypeDesignations()
293 // */
294 // protected void setNameTypeDesignations(Set<NameTypeDesignation> nameTypeDesignations) {
295 // this.nameTypeDesignations = nameTypeDesignations;
296 // }
297 // /**
298 // * Adds a new {@link NameTypeDesignation name type designation} to the set
299 // * of name type designations assigned to <i>this</i> homotypical group and eventually
300 // * (with a boolean parameter) also to the corresponding set of each of the
301 // * {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group.
302 // *
303 // * @param nameTypeDesignation the name type designation to be added
304 // * @param addToAllNames the boolean flag indicating whether the addition will also
305 // * carried out for each taxon name
306 // *
307 // * @see TaxonNameBase#getNameTypeDesignations()
308 // * @see NameTypeDesignation
309 // */
310 // public void addNameTypeDesignation(NameTypeDesignation nameTypeDesignation, boolean addToAllNames) {
311 // if (nameTypeDesignation != null){
312 // nameTypeDesignation.setHomotypicalGroup(this);
313 // nameTypeDesignations.add(nameTypeDesignation);
314 // }
315 // if (addToAllNames){
316 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
317 // taxonNameBase.addNameTypeDesignation(nameTypeDesignation);
318 // }
319 // }
320 // }
321 // /**
322 // * Removes one element from the set of {@link NameTypeDesignation name type designations} assigned to the
323 // * {@link HomotypicalGroup homotypical group} to which this {@link TaxonNameBase taxon name} belongs.
324 // * The same element will be removed from the corresponding set of each of
325 // * the taxon names belonging to <i>this</i> homotypical group. Furthermore the
326 // * homotypical group attribute of the name type designation will be
327 // * nullified.
328 // *
329 // * @param nameTypeDesignation the name type designation which should be deleted
330 // * @see #getNameTypeDesignations()
331 // * @see #addNameTypeDesignation(NameTypeDesignation, boolean)
332 // * @see TaxonNameBase#removeNameTypeDesignation(NameTypeDesignation)
333 // * @see NameTypeDesignation#getHomotypicalGroup()
334 // */
335 // public void removeNameTypeDesignation(NameTypeDesignation nameTypeDesignation) {
336 // if (nameTypeDesignation != null){
337 // nameTypeDesignation.setHomotypicalGroup(null);
338 // nameTypeDesignations.remove(nameTypeDesignation);
339 // }
340 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
341 // taxonNameBase.removeNameTypeDesignation(nameTypeDesignation);
342 // }
343 // }
344
345
346 /**
347 * Retrieves the ordered list (depending on the date of publication) of
348 * {@link taxon.Synonym synonyms} (according to a given reference)
349 * the {@link TaxonNameBase taxon names} of which belong to <i>this</i> homotypical group.
350 * If other names are part of <i>this</i> group that are not considered synonyms
351 * according to the respective reference, then they will not be included in
352 * the result set.
353 *
354 * @param sec the reference whose treatment is to be considered
355 * @return the ordered list of synonyms
356 * @see TaxonNameBase#getSynonyms()
357 * @see TaxonNameBase#getTaxa()
358 * @see taxon.Synonym
359 */
360 public List<Synonym> getSynonymsInGroup(ReferenceBase sec){
361 List<Synonym> result = new ArrayList();
362 for (TaxonNameBase<?, ?>name : this.getTypifiedNames()){
363 for (Synonym synonym : name.getSynonyms()){
364 if ( (synonym.getSec() == null && sec == null) ||
365 synonym.getSec() != null && synonym.getSec().equals(sec)){
366 result.add(synonym);
367 }
368 }
369 }
370 Collections.sort(result, new TaxonComparator());
371 return result;
372 }
373
374
375 /**
376 * Creates a basionym relationship to all other names in this names homotypical
377 * group.
378 *
379 * @see HomotypicalGroup.setGroupBasionym(TaxonNameBase basionymName)
380 *
381 * @param basionymName
382 * @throws IllegalArgumentException if basionymName is not member in this homotypical group
383 */
384 public void setGroupBasionym(TaxonNameBase basionymName) throws IllegalArgumentException{
385 setGroupBasionym(basionymName, null, null, null);
386 }
387
388 public void setGroupBasionym(TaxonNameBase basionymName, ReferenceBase citation, String microCitation, String ruleConsidered)
389 throws IllegalArgumentException {
390 if (! typifiedNames.contains(basionymName)){
391 throw new IllegalArgumentException("Name to be set as basionym/original combination must be part of the homotypical group but is not");
392 }
393 if (typifiedNames.size() < 2){return;}
394 //
395 //Add new relations
396 for (TaxonNameBase name : typifiedNames) {
397 if (!name.equals(basionymName)) {
398 name.addRelationshipFromName(basionymName, NameRelationshipType.BASIONYM(), citation, microCitation, ruleConsidered);
399 }
400 }
401 }
402
403 /**
404 * Removes all basionym relationships between basionymName and any other name
405 * in its homotypic group
406 *
407 * @param basionymName
408 */
409 public static void removeGroupBasionym(TaxonNameBase basionymName) {
410 HomotypicalGroup homotypicalGroup = basionymName.getHomotypicalGroup();
411 Set<NameRelationship> relations = basionymName.getRelationsFromThisName();
412 Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
413
414 for (NameRelationship relation : relations) {
415
416 // If this is a basionym relation, and toName is in the homotypical group,
417 // remove the relationship.
418 if (relation.getType().isBasionymRelation() &&
419 relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
420 removeRelations.add(relation);
421 }
422 }
423
424 // Removing relations from a set through which we are iterating causes a
425 // ConcurrentModificationException. Therefore, we delete the targeted
426 // relations in a second step.
427 for (NameRelationship relation : removeRelations) {
428 basionymName.removeNameRelationship(relation);
429 }
430 }
431
432
433 /**
434 * Returns all taxon names in the homotypical group that do not have an 'is_basionym_for' (zool.: 'is_original_combination_for')
435 * or a replaced synonym relationship.
436 * @return
437 */
438 @Transient
439 public Set<TaxonNameBase> getUnrelatedNames(){
440 Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, true);
441 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
442 result.addAll(this.getTypifiedNames());
443 for (NameRelationship nameRelationship : set){
444 result.remove(nameRelationship.getFromName());
445 result.remove(nameRelationship.getToName());
446 }
447 return result;
448 }
449
450 /**
451 * Returns all taxon names in the homotypical group that are new combinations (have a basionym/original combination
452 * or a replaced synonym).
453 * @return
454 */
455 @Transient
456 public Set<TaxonNameBase> getNewCombinations(){
457 Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, true);
458 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
459 for (NameRelationship nameRelationship : set){
460 result.add(nameRelationship.getToName());
461 }
462 return result;
463 }
464
465
466
467 /**
468 * Returns all taxon names in the homotypical group that have an 'is_basionym_for' (zool.: 'is_original_combination_for')
469 * or a replaced synonym relationship.
470 * @return
471 */
472 @Transient
473 public Set<TaxonNameBase> getBasionymsOrReplacedSynonyms(){
474 Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, true);
475 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
476 for (NameRelationship nameRelationship : set){
477 result.add(nameRelationship.getFromName());
478 }
479 return result;
480 }
481
482 /**
483 * Returns all taxon names in the homotypical group that have a 'is_basionym_for' (zool.: 'is_original_combination_for') relationship.
484 * @return
485 */
486 @Transient
487 public Set<TaxonNameBase> getBasionyms(){
488 Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, false);
489 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
490 for (NameRelationship nameRelationship : set){
491 result.add(nameRelationship.getFromName());
492 }
493 return result;
494 }
495
496 /**
497 * Returns all taxon names in the homotypical group that have a 'is_replaced_synonym_for' relationship.
498 * @return
499 */
500 @Transient
501 public Set<TaxonNameBase> getReplacedSynonym(){
502 Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(false, true);
503 Set<TaxonNameBase> result = new HashSet<TaxonNameBase>();
504 for (NameRelationship nameRelationship : set){
505 result.add(nameRelationship.getFromName());
506 }
507 return result;
508 }
509
510 /**
511 * Returns the name relationships that represent either a basionym (original combination) relationship or
512 * a replaced synonym relationship.
513 * @return
514 */
515 @Transient
516 public Set<NameRelationship> getBasionymAndReplacedSynonymRelations(){
517 return getBasionymOrReplacedSynonymRelations(true, true);
518 }
519
520 /**
521 * Computes all basionym and replaced synonym relationships between names in this group.
522 * If <code>doBasionym</code> is <code>false</code> basionym relationships are excluded.
523 * If <code>doReplacedSynonym</code> is <code>false</code> replaced synonym relationships are excluded.
524 * @param doBasionym
525 * @param doReplacedSynonym
526 * @return
527 */
528 @Transient
529 private Set<NameRelationship> getBasionymOrReplacedSynonymRelations(boolean doBasionym, boolean doReplacedSynonym){
530 Set<NameRelationship> result = new HashSet<NameRelationship>();
531 Set<TaxonNameBase> names = this.getTypifiedNames();
532 if (names.size() > 1){
533 for (TaxonNameBase name : names){
534 Set nameRels = name.getNameRelations();
535 //TODO make getNameRelations generic
536 for (Object obj : nameRels){
537 NameRelationship nameRel = (NameRelationship)obj;
538 NameRelationshipType type = nameRel.getType();
539 if ( type.isBasionymRelation() && doBasionym){
540 if (testRelatedNameInThisGroup(nameRel)){
541 result.add(nameRel);
542 }else{
543 logger.warn("Name has basionym relation to a name that is not in the same homotypical group");
544 }
545 }else if (type.isReplacedSynonymRelation() && doReplacedSynonym) {
546 if (testRelatedNameInThisGroup(nameRel)){
547 result.add(nameRel);
548 }else{
549 logger.warn("Name has replaced synonym relation to a name that is not in the same homotypical group");
550 }
551 }
552 }
553 }
554 }
555 return result;
556 }
557
558 private boolean testRelatedNameInThisGroup(NameRelationship nameRel){
559 TaxonNameBase toName = nameRel.getToName();
560 return (this.getTypifiedNames().contains(toName));
561 }
562
563 private boolean isBasionymOrRepSynRel(NameRelationshipType relType){
564 if (relType == null){
565 throw new IllegalArgumentException("NameRelationshipType should never be null");
566 }else if (relType.equals(NameRelationshipType.BASIONYM())) {
567 return true;
568 }else if (relType.equals(NameRelationshipType.REPLACED_SYNONYM())){
569 return true;
570 }else{
571 return false;
572 }
573 }
574 }