2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.cdm
.model
.name
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Collections
;
14 import java
.util
.HashSet
;
15 import java
.util
.List
;
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
;
30 import org
.apache
.log4j
.Logger
;
31 import org
.hibernate
.envers
.Audited
;
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
;
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
57 * <li> Names that share the same basionym or replaced synonym must belong to
58 * the same homotypical group
61 * @see TypeDesignationBase
62 * @see NameTypeDesignation
63 * @see SpecimenTypeDesignation
66 * @created 08-Nov-2007
68 @XmlAccessorType(XmlAccessType
.FIELD
)
69 @XmlType(name
= "HomotypicalGroup", propOrder
= {
74 public class HomotypicalGroup
extends AnnotatableEntity
{
75 private static final Logger logger
= Logger
.getLogger(HomotypicalGroup
.class);
77 @XmlElementWrapper(name
= "TypifiedNames")
78 @XmlElement(name
= "TypifiedName")
80 @XmlSchemaType(name
= "IDREF")
81 @OneToMany(mappedBy
="homotypicalGroup", fetch
=FetchType
.LAZY
)
82 protected Set
<TaxonNameBase
> typifiedNames
= new HashSet
<TaxonNameBase
>();
84 // ******************** static methods **************************************/
86 * Creates a new homotypical group instance with an empty set of typified
87 * {@link TaxonNameBase taxon names}.
89 * @see #HomotypicalGroup()
91 public static HomotypicalGroup
NewInstance(){
92 return new HomotypicalGroup();
96 //********************** CONSTRUCTOR ********************************************/
99 * Class constructor: creates a new homotypical group instance with an
100 * empty set of typified {@link TaxonNameBase taxon names}.
102 public HomotypicalGroup() {
106 // ********************** GETTER/SETTER/ADDER/REMOVER ********************************/
109 * Returns the set of {@link TaxonNameBase taxon names} that belong to <i>this</i> homotypical group.
111 * @see #getSpecimenTypeDesignations()
113 public Set
<TaxonNameBase
> getTypifiedNames() {
114 return typifiedNames
;
118 * Adds a new {@link TaxonNameBase taxon name} to the set of taxon names that belong
119 * to <i>this</i> homotypical group.
121 * @param typifiedName the taxon name to be added to <i>this</i> group
122 * @see #getTypifiedNames()
123 * @see #removeTypifiedName(TaxonNameBase)
125 public void addTypifiedName(TaxonNameBase typifiedName
) {
126 if (typifiedName
!= null){
127 typifiedNames
.add(typifiedName
);
128 typifiedName
.setHomotypicalGroup(this);
132 * Removes one element from the set of {@link TaxonNameBase taxon names}
133 * that belong to <i>this</i> homotypical group.
135 * @param taxonBase the taxon name which should be removed from the corresponding set
136 * @see #addTypifiedName(TaxonNameBase)
138 public void removeTypifiedName(TaxonNameBase typifiedName
) {
139 HomotypicalGroup newHomotypicalGroup
= HomotypicalGroup
.NewInstance();
140 typifiedName
.setHomotypicalGroup(newHomotypicalGroup
);
141 typifiedNames
.remove(typifiedName
);
145 * Merges the typified {@link TaxonNameBase taxon names} from one homotypical group into
146 * the set of typified taxon names of <i>this</i> homotypical group.
148 * @param homotypicalGroupToMerge the homotypical group the typified names of which
149 * are to be transferred to <i>this</i> homotypical group
151 public void merge(HomotypicalGroup homotypicalGroupToMerge
){
152 if (homotypicalGroupToMerge
!= null){
153 Set
<TaxonNameBase
> typifiedNames
= new HashSet
<TaxonNameBase
>();
154 typifiedNames
.addAll(homotypicalGroupToMerge
.getTypifiedNames());
155 for (TaxonNameBase typifiedName
: typifiedNames
){
156 this.addTypifiedName(typifiedName
);
163 * Returns the set of {@link SpecimenTypeDesignation specimen type designations} that
164 * typify the {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group
165 * including the status of these designations.
167 * @see #getTypifiedNames()
168 * @see #getNameTypeDesignations()
169 * @see #getTypeDesignations()
170 * @see TaxonNameBase#getSpecimenTypeDesignations()
173 public Set
<SpecimenTypeDesignation
> getSpecimenTypeDesignations(){
174 Set
<SpecimenTypeDesignation
> result
= new HashSet
<SpecimenTypeDesignation
>();
175 for (TaxonNameBase taxonName
: typifiedNames
){
176 result
.addAll(taxonName
.getSpecimenTypeDesignations());
182 * Returns the set of {@link NameTypeDesignation name type designations} that
183 * typify the {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group
184 * including the status of these designations.
186 * @see #getTypifiedNames()
187 * @see #getSpecimenTypeDesignations()
188 * @see #getTypeDesignations()
189 * @see TaxonNameBase#getNameTypeDesignations()
192 public Set
<NameTypeDesignation
> getNameTypeDesignations(){
193 Set
<NameTypeDesignation
> result
= new HashSet
<NameTypeDesignation
>();
194 for (TaxonNameBase taxonName
: typifiedNames
){
195 result
.addAll(taxonName
.getNameTypeDesignations());
202 * Returns the set of all {@link TypeDesignationBase type designations} that
203 * typify the {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group
204 * (this includes either {@link NameTypeDesignation name type designations} or
205 * {@link SpecimenTypeDesignation specimen type designations}).
207 * @see #getTypifiedNames()
208 * @see #getNameTypeDesignations()
209 * @see #getSpecimenTypeDesignations()
210 * @see TaxonNameBase#getTypeDesignations()
213 public Set
<TypeDesignationBase
> getTypeDesignations(){
214 Set
<TypeDesignationBase
> result
= new HashSet
<TypeDesignationBase
>();
215 for (TaxonNameBase taxonName
: typifiedNames
){
216 result
.addAll(taxonName
.getTypeDesignations());
222 // * Returns the set of {@link SpecimenTypeDesignation specimen type designations} that
223 // * typify <i>this</i> homotypical group including the status of these designations.
225 // * @see #getTypifiedNames()
228 // @Cascade({CascadeType.SAVE_UPDATE})
229 // public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations() {
230 // return specimenTypeDesignations;
233 // * @see #getSpecimenTypeDesignations()
235 // protected void setSpecimenTypeDesignations(Set<SpecimenTypeDesignation> specimenTypeDesignations) {
236 // this.specimenTypeDesignations = specimenTypeDesignations;
239 // * Adds a new {@link SpecimenTypeDesignation specimen type designation} to the set
240 // * of specimen type designations assigned to <i>this</i> homotypical group and eventually
241 // * (with a boolean parameter) also to the corresponding set of each of the
242 // * {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group.
244 // * @param specimenTypeDesignation the specimen type designation to be added
245 // * @param addToAllNames the boolean flag indicating whether the addition will also
246 // * carried out for each taxon name
248 // * @see TaxonNameBase#getSpecimenTypeDesignations()
249 // * @see SpecimenTypeDesignation
251 // public void addSpecimenTypeDesignation(SpecimenTypeDesignation specimenTypeDesignation, boolean addToAllNames) {
252 // if (specimenTypeDesignation != null){
253 // specimenTypeDesignation.setHomotypicalGroup(this);
254 // specimenTypeDesignations.add(specimenTypeDesignation);
256 // if (addToAllNames){
257 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
258 // taxonNameBase.addSpecimenTypeDesignation(specimenTypeDesignation);
263 // * Removes one element from the set of {@link SpecimenTypeDesignation specimen type designations} assigned to the
264 // * {@link HomotypicalGroup homotypical group} to which this {@link TaxonNameBase taxon name} belongs.
265 // * The same element will be removed from the corresponding set of each of
266 // * the taxon names belonging to <i>this</i> homotypical group. Furthermore the
267 // * homotypical group attribute of the specimen type designation will be
270 // * @param specimenTypeDesignation the specimen type designation which should be deleted
271 // * @see #getSpecimenTypeDesignations()
272 // * @see #addSpecimenTypeDesignation(SpecimenTypeDesignation, boolean)
273 // * @see TaxonNameBase#removeSpecimenTypeDesignation(SpecimenTypeDesignation)
274 // * @see SpecimenTypeDesignation#getHomotypicalGroup()
276 // public void removeSpecimenTypeDesignation(SpecimenTypeDesignation specimenTypeDesignation) {
277 // if (specimenTypeDesignation != null){
278 // specimenTypeDesignation.setHomotypicalGroup(null);
279 // specimenTypeDesignations.remove(specimenTypeDesignation);
281 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
282 // taxonNameBase.removeSpecimenTypeDesignation(specimenTypeDesignation);
288 // * Returns the set of {@link NameTypeDesignation name type designations} that
289 // * typify <i>this</i> homotypical group including the status of these designations.
291 // * @see #getTypifiedNames()
294 // @Cascade({CascadeType.SAVE_UPDATE})
295 // public Set<NameTypeDesignation> getNameTypeDesignations() {
296 // return nameTypeDesignations;
299 // * @see #getNameTypeDesignations()
301 // protected void setNameTypeDesignations(Set<NameTypeDesignation> nameTypeDesignations) {
302 // this.nameTypeDesignations = nameTypeDesignations;
305 // * Adds a new {@link NameTypeDesignation name type designation} to the set
306 // * of name type designations assigned to <i>this</i> homotypical group and eventually
307 // * (with a boolean parameter) also to the corresponding set of each of the
308 // * {@link TaxonNameBase taxon names} belonging to <i>this</i> homotypical group.
310 // * @param nameTypeDesignation the name type designation to be added
311 // * @param addToAllNames the boolean flag indicating whether the addition will also
312 // * carried out for each taxon name
314 // * @see TaxonNameBase#getNameTypeDesignations()
315 // * @see NameTypeDesignation
317 // public void addNameTypeDesignation(NameTypeDesignation nameTypeDesignation, boolean addToAllNames) {
318 // if (nameTypeDesignation != null){
319 // nameTypeDesignation.setHomotypicalGroup(this);
320 // nameTypeDesignations.add(nameTypeDesignation);
322 // if (addToAllNames){
323 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
324 // taxonNameBase.addNameTypeDesignation(nameTypeDesignation);
329 // * Removes one element from the set of {@link NameTypeDesignation name type designations} assigned to the
330 // * {@link HomotypicalGroup homotypical group} to which this {@link TaxonNameBase taxon name} belongs.
331 // * The same element will be removed from the corresponding set of each of
332 // * the taxon names belonging to <i>this</i> homotypical group. Furthermore the
333 // * homotypical group attribute of the name type designation will be
336 // * @param nameTypeDesignation the name type designation which should be deleted
337 // * @see #getNameTypeDesignations()
338 // * @see #addNameTypeDesignation(NameTypeDesignation, boolean)
339 // * @see TaxonNameBase#removeNameTypeDesignation(NameTypeDesignation)
340 // * @see NameTypeDesignation#getHomotypicalGroup()
342 // public void removeNameTypeDesignation(NameTypeDesignation nameTypeDesignation) {
343 // if (nameTypeDesignation != null){
344 // nameTypeDesignation.setHomotypicalGroup(null);
345 // nameTypeDesignations.remove(nameTypeDesignation);
347 // for (TaxonNameBase taxonNameBase : this.typifiedNames){
348 // taxonNameBase.removeNameTypeDesignation(nameTypeDesignation);
354 * Retrieves the ordered list (depending on the date of publication) of
355 * {@link taxon.Synonym synonyms} (according to a given reference)
356 * the {@link TaxonNameBase taxon names} of which belong to <i>this</i> homotypical group.
357 * If other names are part of <i>this</i> group that are not considered synonyms
358 * according to the respective reference, then they will not be included in
361 * @deprecated synonyms should not depend on the sec. Therefore this method will be removed in
362 * version 3.1 or higher. Use {@link Taxon#getSynonymsInGroup(HomotypicalGroup)} instead. But be
363 * aware that the semantics is slightly different.
364 * @param sec the reference whose treatment is to be considered
365 * @return the ordered list of synonyms
366 * @see TaxonNameBase#getSynonyms()
367 * @see TaxonNameBase#getTaxa()
371 public List
<Synonym
> getSynonymsInGroup(Reference sec
){
372 List
<Synonym
> result
= new ArrayList
<Synonym
>();
373 for (TaxonNameBase
<?
, ?
>name
: this.getTypifiedNames()){
374 for (Synonym synonym
: name
.getSynonyms()){
375 if ( (synonym
.getSec() == null && sec
== null) ||
376 synonym
.getSec() != null && synonym
.getSec().equals(sec
)){
381 Collections
.sort(result
, new TaxonComparator());
386 * Creates a basionym relationship to all other names in this names homotypical
389 * @see HomotypicalGroup.setGroupBasionym(TaxonNameBase basionymName)
391 * @param basionymName
392 * @throws IllegalArgumentException if basionymName is not member in this homotypical group
394 public void setGroupBasionym(TaxonNameBase basionymName
) throws IllegalArgumentException
{
395 setGroupBasionym(basionymName
, null, null, null);
398 public void setGroupBasionym(TaxonNameBase basionymName
, Reference citation
, String microCitation
, String ruleConsidered
)
399 throws IllegalArgumentException
{
400 if (! typifiedNames
.contains(basionymName
)){
401 throw new IllegalArgumentException("Name to be set as basionym/original combination must be part of the homotypical group but is not");
403 if (typifiedNames
.size() < 2){return;}
406 for (TaxonNameBase name
: typifiedNames
) {
407 if (!name
.equals(basionymName
)) {
408 name
.addRelationshipFromName(basionymName
, NameRelationshipType
.BASIONYM(), citation
, microCitation
, ruleConsidered
);
414 * Removes all basionym relationships between basionymName and any other name
415 * in its homotypic group
417 * @param basionymName
419 public static void removeGroupBasionym(TaxonNameBase basionymName
) {
420 HomotypicalGroup homotypicalGroup
= basionymName
.getHomotypicalGroup();
421 Set
<NameRelationship
> relations
= basionymName
.getRelationsFromThisName();
422 Set
<NameRelationship
> removeRelations
= new HashSet
<NameRelationship
>();
424 for (NameRelationship relation
: relations
) {
426 // If this is a basionym relation, and toName is in the homotypical group,
427 // remove the relationship.
428 if (relation
.getType().isBasionymRelation() &&
429 relation
.getToName().getHomotypicalGroup().equals(homotypicalGroup
)) {
430 removeRelations
.add(relation
);
434 // Removing relations from a set through which we are iterating causes a
435 // ConcurrentModificationException. Therefore, we delete the targeted
436 // relations in a second step.
437 for (NameRelationship relation
: removeRelations
) {
438 basionymName
.removeNameRelationship(relation
);
444 * Returns all taxon names in the homotypical group that do not have an 'is_basionym_for' (zool.: 'is_original_combination_for')
445 * or a replaced synonym relationship.
449 public Set
<TaxonNameBase
> getUnrelatedNames(){
450 Set
<NameRelationship
> set
= getBasionymOrReplacedSynonymRelations(true, true);
451 Set
<TaxonNameBase
> result
= new HashSet
<TaxonNameBase
>();
452 result
.addAll(this.getTypifiedNames());
453 for (NameRelationship nameRelationship
: set
){
454 result
.remove(nameRelationship
.getFromName());
455 result
.remove(nameRelationship
.getToName());
461 * Returns all taxon names in the homotypical group that are new combinations (have a basionym/original combination
462 * or a replaced synonym).
466 public Set
<TaxonNameBase
> getNewCombinations(){
467 Set
<NameRelationship
> set
= getBasionymOrReplacedSynonymRelations(true, true);
468 Set
<TaxonNameBase
> result
= new HashSet
<TaxonNameBase
>();
469 for (NameRelationship nameRelationship
: set
){
470 result
.add(nameRelationship
.getToName());
478 * Returns all taxon names in the homotypical group that have an 'is_basionym_for' (zool.: 'is_original_combination_for')
479 * or a replaced synonym relationship.
483 public Set
<TaxonNameBase
> getBasionymsOrReplacedSynonyms(){
484 Set
<NameRelationship
> set
= getBasionymOrReplacedSynonymRelations(true, true);
485 Set
<TaxonNameBase
> result
= new HashSet
<TaxonNameBase
>();
486 for (NameRelationship nameRelationship
: set
){
487 result
.add(nameRelationship
.getFromName());
493 * Returns all taxon names in the homotypical group that have a 'is_basionym_for' (zool.: 'is_original_combination_for') relationship.
497 public Set
<TaxonNameBase
> getBasionyms(){
498 Set
<NameRelationship
> set
= getBasionymOrReplacedSynonymRelations(true, false);
499 Set
<TaxonNameBase
> result
= new HashSet
<TaxonNameBase
>();
500 for (NameRelationship nameRelationship
: set
){
501 result
.add(nameRelationship
.getFromName());
507 * Returns all taxon names in the homotypical group that have a 'is_replaced_synonym_for' relationship.
511 public Set
<TaxonNameBase
> getReplacedSynonym(){
512 Set
<NameRelationship
> set
= getBasionymOrReplacedSynonymRelations(false, true);
513 Set
<TaxonNameBase
> result
= new HashSet
<TaxonNameBase
>();
514 for (NameRelationship nameRelationship
: set
){
515 result
.add(nameRelationship
.getFromName());
521 * Returns the name relationships that represent either a basionym (original combination) relationship or
522 * a replaced synonym relationship.
526 public Set
<NameRelationship
> getBasionymAndReplacedSynonymRelations(){
527 return getBasionymOrReplacedSynonymRelations(true, true);
531 * Computes all basionym and replaced synonym relationships between names in this group.
532 * If <code>doBasionym</code> is <code>false</code> basionym relationships are excluded.
533 * If <code>doReplacedSynonym</code> is <code>false</code> replaced synonym relationships are excluded.
535 * @param doReplacedSynonym
539 private Set
<NameRelationship
> getBasionymOrReplacedSynonymRelations(boolean doBasionym
, boolean doReplacedSynonym
){
540 Set
<NameRelationship
> result
= new HashSet
<NameRelationship
>();
541 Set
<TaxonNameBase
> names
= this.getTypifiedNames();
542 if (names
.size() > 1){
543 for (TaxonNameBase name
: names
){
544 Set nameRels
= name
.getNameRelations();
545 //TODO make getNameRelations generic
546 for (Object obj
: nameRels
){
547 NameRelationship nameRel
= (NameRelationship
)obj
;
548 NameRelationshipType type
= nameRel
.getType();
549 if ( type
.isBasionymRelation() && doBasionym
){
550 if (testRelatedNameInThisGroup(nameRel
)){
553 logger
.warn("Name has basionym relation to a name that is not in the same homotypical group");
555 }else if (type
.isReplacedSynonymRelation() && doReplacedSynonym
) {
556 if (testRelatedNameInThisGroup(nameRel
)){
559 logger
.warn("Name has replaced synonym relation to a name that is not in the same homotypical group");
568 private boolean testRelatedNameInThisGroup(NameRelationship nameRel
){
569 TaxonNameBase toName
= nameRel
.getToName();
570 return (this.getTypifiedNames().contains(toName
));
573 private boolean isBasionymOrRepSynRel(NameRelationshipType relType
){
574 if (relType
== null){
575 throw new IllegalArgumentException("NameRelationshipType should never be null");
576 }else if (relType
.equals(NameRelationshipType
.BASIONYM())) {
578 }else if (relType
.equals(NameRelationshipType
.REPLACED_SYNONYM())){