Project

General

Profile

Download (16.3 KB) Statistics
| Branch: | Tag: | Revision:
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
package eu.etaxonomy.cdm.model.name;
10

    
11
import java.util.HashSet;
12
import java.util.Set;
13

    
14
import javax.persistence.Entity;
15
import javax.persistence.FetchType;
16
import javax.persistence.OneToMany;
17
import javax.persistence.Transient;
18
import javax.xml.bind.annotation.XmlAccessType;
19
import javax.xml.bind.annotation.XmlAccessorType;
20
import javax.xml.bind.annotation.XmlElement;
21
import javax.xml.bind.annotation.XmlElementWrapper;
22
import javax.xml.bind.annotation.XmlIDREF;
23
import javax.xml.bind.annotation.XmlSchemaType;
24
import javax.xml.bind.annotation.XmlType;
25

    
26
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
27
import org.hibernate.envers.Audited;
28

    
29
import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
30
import eu.etaxonomy.cdm.model.reference.Reference;
31

    
32
/**
33
 * The homotypical group class represents a set of {@link TaxonName taxon names} associated
34
 * on the base of their typifications. Since it can be asserted that two taxon
35
 * names are typified by the same type without mentioning the type itself, even
36
 * taxon names without explicit {@link TypeDesignationBase type designation} can belong
37
 * to an homotypical group.<BR>
38
 * Taxon names belonging to an homotypical group and the taxon names or
39
 * {@link eu.etaxonomy.cdm.model.occurrence.DerivedUnit specimens} used as types for their
40
 * {@link TypeDesignationBase type designations} have the following properties: <ul>
41
 * <li>	A taxon name belongs exactly to one homotypical group
42
 * <li>	A type specimen or a type name can be used as a type only for taxon
43
 * 		names belonging to the same homotypical group<BR>
44
 * 		- therefore an homotypical group circumscribes a set of types<BR>
45
 * 		- each taxon name shares a subset of these types<BR>
46
 * 		- each type is used by a subset of these taxon names
47
 * 			within the homotypical group
48
 * <li>	Names that share at least one common type must belong to the same
49
 * 		homotypical group
50
 * <li>	Names that share the same basionym or replaced synonym must belong to
51
 * 		the same homotypical group
52
 * </ul>
53
 *
54
 * @see		TypeDesignationBase
55
 * @see		NameTypeDesignation
56
 * @see		SpecimenTypeDesignation
57
 * @author  m.doering
58
 * @version 1.0
59
 * @since 08-Nov-2007
60
 */
61
@XmlAccessorType(XmlAccessType.FIELD)
62
@XmlType(name = "HomotypicalGroup", propOrder = {
63
    "typifiedNames"
64
})
65
@Entity
66
@Audited
67
public class HomotypicalGroup extends AnnotatableEntity {
68
	private static final long serialVersionUID = -2308347613205551766L;
69

    
70
	private static final Logger logger = LogManager.getLogger(HomotypicalGroup.class);
71

    
72
	@XmlElementWrapper(name = "TypifiedNames")
73
	@XmlElement(name = "TypifiedName")
74
	@XmlIDREF
75
	@XmlSchemaType(name = "IDREF")
76
	@OneToMany(mappedBy="homotypicalGroup", fetch=FetchType.LAZY)
77
	protected Set<TaxonName> typifiedNames = new HashSet<>();
78

    
79
// ******************** static methods **************************************/
80
	/**
81
	 * Creates a new homotypical group instance with an empty set of typified
82
	 * {@link TaxonName taxon names}.
83
	 *
84
	 * @see #HomotypicalGroup()
85
	 */
86
	public static HomotypicalGroup NewInstance(){
87
		return new HomotypicalGroup();
88
	}
89

    
90

    
91
//********************** CONSTRUCTOR ********************************************/
92

    
93
	/**
94
	 * Class constructor: creates a new homotypical group instance with an
95
	 * empty set of typified {@link TaxonName taxon names}.
96
	 */
97
	protected HomotypicalGroup() {
98
		super();
99
	}
100

    
101
// ********************** GETTER/SETTER/ADDER/REMOVER ********************************/
102

    
103
	/**
104
	 * Returns the set of {@link TaxonName taxon names} that belong to <i>this</i> homotypical group.
105
	 *
106
	 * @see	#getSpecimenTypeDesignations()
107
	 */
108
	public Set<TaxonName> getTypifiedNames() {
109
		return typifiedNames;
110
	}
111

    
112
	/**
113
	 * Adds a new {@link TaxonName taxon name} to the set of taxon names that belong
114
	 * to <i>this</i> homotypical group.
115
	 *
116
	 * @param  typifiedName  the taxon name to be added to <i>this</i> group
117
	 * @see 			  	 #getTypifiedNames()
118
	 * @see 			  	 #removeTypifiedName(TaxonName)
119
	 */
120
	public void addTypifiedName(TaxonName typifiedName) {
121
		if (typifiedName != null){
122
			typifiedNames.add(typifiedName);
123
			//if (typifiedName.getHomotypicalGroup() != null && !typifiedName.getHomotypicalGroup().equals(this))
124
			typifiedName.setHomotypicalGroup(this);
125
		}
126
	}
127

    
128
	/**
129
	 * @see #removeTypifiedName(TaxonName, boolean)
130
	 * @param typifiedName
131
	 */
132
	public void removeTypifiedName(TaxonName typifiedName) {
133
		removeTypifiedName(typifiedName, false);
134
	}
135

    
136

    
137
	/**
138
	 * Removes one element from the set of {@link TaxonName taxon names}
139
	 * that belong to <i>this</i> homotypical group.
140
	 *
141
	 * @param  typifiedName	the taxon name which should be removed from the corresponding set
142
	 * @param  removeGroup  if <code>true</code> the typified name is given a new
143
	 * 						homotypical group
144
	 * @see    #addTypifiedName(TaxonName)
145
	 */
146
	public void removeTypifiedName(TaxonName typifiedName, boolean removeGroup) {
147
		if (removeGroup){
148
			HomotypicalGroup newHomotypicalGroup = HomotypicalGroup.NewInstance();
149
			typifiedName.setHomotypicalGroup(newHomotypicalGroup);
150
		}
151

    
152
		typifiedNames.remove(typifiedName);
153
	}
154

    
155
	/**
156
	 * Merges the typified {@link TaxonName taxon names} from one homotypical group into
157
	 * the set of typified taxon names of <i>this</i> homotypical group.
158
	 *
159
	 * @param	homotypicalGroupToMerge the homotypical group the typified names of which
160
	 * 									are to be transferred to <i>this</i> homotypical group
161
	 */
162
	public void merge(HomotypicalGroup homotypicalGroupToMerge){
163
		if (homotypicalGroupToMerge != null){
164
			Set<TaxonName> typifiedNames = new HashSet<>();
165
			typifiedNames.addAll(homotypicalGroupToMerge.getTypifiedNames());
166
			for (TaxonName typifiedName: typifiedNames){
167
				this.addTypifiedName(typifiedName);
168
			}
169
		}
170
	}
171

    
172

    
173
	/**
174
	 * Returns the set of {@link SpecimenTypeDesignation specimen type designations} that
175
	 * typify the {@link TaxonName taxon names} belonging to <i>this</i> homotypical group
176
	 * including the status of these designations.
177
	 *
178
	 * @see	#getTypifiedNames()
179
	 * @see	#getNameTypeDesignations()
180
	 * @see	#getTypeDesignations()
181
	 * @see	TaxonName#getSpecimenTypeDesignations()
182
	 */
183
	@Transient
184
	public Set<SpecimenTypeDesignation> getSpecimenTypeDesignations(){
185
		Set<SpecimenTypeDesignation> result = new HashSet<SpecimenTypeDesignation>();
186
		for (TaxonName taxonName : typifiedNames){
187
			result.addAll(taxonName.getSpecimenTypeDesignations());
188
		}
189
		return result;
190
	}
191

    
192
	/**
193
	 * Returns the set of {@link NameTypeDesignation name type designations} that
194
	 * typify the {@link TaxonName taxon names} belonging to <i>this</i> homotypical group
195
	 * including the status of these designations.
196
	 *
197
	 * @see	#getTypifiedNames()
198
	 * @see	#getSpecimenTypeDesignations()
199
	 * @see	#getTypeDesignations()
200
	 * @see	TaxonName#getNameTypeDesignations()
201
	 */
202
	@Transient
203
	public Set<NameTypeDesignation> getNameTypeDesignations(){
204
		Set<NameTypeDesignation> result = new HashSet<NameTypeDesignation>();
205
		for (TaxonName taxonName : typifiedNames){
206
			result.addAll(taxonName.getNameTypeDesignations());
207
		}
208
		return result;
209
	}
210

    
211

    
212
	/**
213
	 * Returns the set of all {@link TypeDesignationBase type designations} that
214
	 * typify the {@link TaxonName taxon names} belonging to <i>this</i> homotypical group
215
	 * (this includes either {@link NameTypeDesignation name type designations} or
216
	 * {@link SpecimenTypeDesignation specimen type designations}).
217
	 *
218
	 * @see	#getTypifiedNames()
219
	 * @see	#getNameTypeDesignations()
220
	 * @see	#getSpecimenTypeDesignations()
221
	 * @see	TaxonName#getTypeDesignations()
222
	 */
223
	@Transient
224
	public Set<TypeDesignationBase<?>> getTypeDesignations(){
225
		Set<TypeDesignationBase<?>> result = new HashSet<>();
226
		for (TaxonName taxonName : typifiedNames){
227
			result.addAll((Set)taxonName.getTypeDesignations());
228
		}
229
		return result;
230
	}
231

    
232

    
233
    /**
234
     * Creates a basionym relationship to all other names in this names homotypical
235
     * group.
236
     *
237
     * @see HomotypicalGroup.setGroupBasionym(TaxonName basionymName)
238
     *
239
     * @param basionymName
240
     * @throws IllegalArgumentException if basionymName is not member in this homotypical group
241
     */
242
	@Transient
243
	public void setGroupBasionym(TaxonName basionymName) throws IllegalArgumentException{
244
    	setGroupBasionym(basionymName, null, null, null, null);
245
    }
246

    
247
	public void setGroupBasionym(TaxonName basionymName, Reference citation, String microCitation, String ruleConsidered, NomenclaturalCodeEdition codeEdition)
248
    			throws IllegalArgumentException {
249
    	if (! typifiedNames.contains(basionymName)){
250
        	throw new IllegalArgumentException("Name to be set as basionym/original combination must be part of the homotypical group but is not");
251
        }
252
        if (typifiedNames.size() < 2){return;}
253
//
254
    	//Add new relations
255
        Set<TaxonName> typified = new HashSet<>();
256
        for (TaxonName name : typifiedNames){
257
        	typified.add(name);
258
        }
259
        for (TaxonName name : typified) {
260
    		if (!name.equals(basionymName)) {
261
		    	name.addRelationshipFromName(basionymName, NameRelationshipType.BASIONYM(), citation, microCitation, ruleConsidered, codeEdition);
262
			}
263
    	}
264
        typifiedNames= typified;
265
    }
266

    
267

    
268

    
269
    /**
270
     * Removes all basionym relationships between basionymName and any other name
271
     * in it's homotypic group
272
     *
273
     * @param basionymName
274
     */
275
     public static void removeGroupBasionym(TaxonName basionymName) {
276
    	 HomotypicalGroup homotypicalGroup = basionymName.getHomotypicalGroup();
277
         Set<NameRelationship> relations = basionymName.getRelationsFromThisName();
278
         Set<NameRelationship> removeRelations = new HashSet<NameRelationship>();
279

    
280
         for (NameRelationship relation : relations) {
281

    
282
                 // If this is a basionym relation, and toName is in the homotypical group,
283
                 //      remove the relationship.
284
                 if (relation.getType().isBasionymRelation() &&
285
                                 relation.getToName().getHomotypicalGroup().equals(homotypicalGroup)) {
286
                         removeRelations.add(relation);
287
                 }
288
         }
289

    
290
          // Removing relations from a set through which we are iterating causes a
291
          //      ConcurrentModificationException. Therefore, we delete the targeted
292
          //      relations in a second step.
293
          for (NameRelationship relation : removeRelations) {
294
                  basionymName.removeNameRelationship(relation);
295
          }
296
     }
297

    
298
//     /**
299
//     * TODO
300
//     * This method tries to set basionym(s) for a homotypical group
301
//     * Rule: Basionym must not have basionym author and not protectedAuthorshipCache
302
//     *       Others must have basionymAuthor
303
//     *       BasionymAuhtors must have equal NomenclaturalTitleCache to basionym
304
//       *     last epithet must fit (be equal except for ending), otherwise we expect it to be replaced synonym
305
//       *     those names fitting to a basionym relationship must remove all other basionym (and replaced synonym?) relationships
306
//       * Open: All types of relationships to replaced synonyms
307
//     */
308
//    public void guessAndSetBasionym(){
309
//         Map<String, INonViralName> candidates = new HashMap<>();
310
//         for (TaxonName typifiedName : this.typifiedNames){
311
//             if (! typifiedName.protectedAuthorshipCache && nvn.getBasionymAuthorship() == null){
312
//                 candidates.add(typifiedName);
313
//             }
314
//         }
315
//         if (candidates.size() == 1){
316
//             for (TaxonName typifiedName : this.typifiedNames){
317
//                 removeGroupBasionym(typifiedName);
318
//             }
319
//             this.setGroupBasionym(candidates.iterator().next());
320
//         }
321
//
322
//     }
323

    
324

    
325
	/**
326
	 * Returns all taxon names in the homotypic group that do not have an
327
	 * 'is_basionym_for' (zool.: 'is_original_combination_for')
328
	 * or a replaced synonym relationship.
329
	 * @return
330
	 */
331
	@Transient
332
	public Set<TaxonName> getUnrelatedNames(){
333
		Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, true);
334
		Set<TaxonName> result = new HashSet<>();
335
		result.addAll(this.getTypifiedNames());
336
		for (NameRelationship nameRelationship : set){
337
			result.remove(nameRelationship.getFromName());
338
			result.remove(nameRelationship.getToName());
339
		}
340
		return result;
341
	}
342

    
343
	/**
344
	 * Returns all taxon names in the homotypical group that are new combinations (have a basionym/original combination
345
	 * or a replaced synonym).
346
	 * @return
347
	 */
348
	@Transient
349
	public Set<TaxonName> getNewCombinations(){
350
		Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, true);
351
		Set<TaxonName> result = new HashSet<>();
352
		for (NameRelationship nameRelationship : set){
353
			result.add(nameRelationship.getToName());
354
		}
355
		return result;
356
	}
357

    
358

    
359

    
360
	/**
361
	 * Returns all taxon names in the homotypical group that have an 'is_basionym_for' (zool.: 'is_original_combination_for')
362
	 * or a replaced synonym relationship.
363
	 * @return
364
	 */
365
	@Transient
366
	public Set<TaxonName> getBasionymsOrReplacedSynonyms(){
367
		Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, true);
368
		Set<TaxonName> result = new HashSet<>();
369
		for (NameRelationship nameRelationship : set){
370
			result.add(nameRelationship.getFromName());
371
		}
372
		return result;
373
	}
374

    
375
	/**
376
	 * Returns all taxon names in the homotypical group that have a 'is_basionym_for' (zool.: 'is_original_combination_for') relationship.
377
	 */
378
	@Transient
379
	public Set<TaxonName> getBasionyms(){
380
		Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(true, false);
381
		Set<TaxonName> result = new HashSet<>();
382
		for (NameRelationship nameRelationship : set){
383
			result.add(nameRelationship.getFromName());
384
		}
385
		return result;
386
	}
387

    
388
	/**
389
	 * Returns all taxon names in the homotypical group that have a 'is_replaced_synonym_for' relationship.
390
	 */
391
	@Transient
392
	public Set<TaxonName> getReplacedSynonym(){
393
		Set<NameRelationship> set = getBasionymOrReplacedSynonymRelations(false, true);
394
		Set<TaxonName> result = new HashSet<>();
395
		for (NameRelationship nameRelationship : set){
396
			result.add(nameRelationship.getFromName());
397
		}
398
		return result;
399
	}
400

    
401
	/**
402
	 * Returns the name relationships that represent either a basionym (original combination) relationship or
403
	 * a replaced synonym relationship.
404
	 */
405
	@Transient
406
	public Set<NameRelationship> getBasionymAndReplacedSynonymRelations(){
407
		return getBasionymOrReplacedSynonymRelations(true, true);
408
	}
409

    
410
	/**
411
	 * Computes all basionym and replaced synonym relationships between names in this group.
412
	 * If <code>doBasionym</code> is <code>false</code> basionym relationships are excluded.
413
	 * If <code>doReplacedSynonym</code> is <code>false</code> replaced synonym relationships are excluded.
414
	 * @param doBasionym
415
	 * @param doReplacedSynonym
416
	 * @return
417
	 */
418
	@Transient
419
	private Set<NameRelationship> getBasionymOrReplacedSynonymRelations(boolean doBasionym, boolean doReplacedSynonym){
420
		Set<NameRelationship> result = new HashSet<NameRelationship>();
421
		Set<TaxonName> names = this.getTypifiedNames();
422
		if (names.size() > 1){
423
			for (TaxonName name : names){
424
				Set nameRels = name.getNameRelations();
425
				//TODO make getNameRelations generic
426
				for (Object obj : nameRels){
427
					NameRelationship nameRel = (NameRelationship)obj;
428
					NameRelationshipType type = nameRel.getType();
429
					if ( type.isBasionymRelation() && doBasionym){
430
						if (testRelatedNameInThisGroup(nameRel)){
431
							result.add(nameRel);
432
						}else{
433
							logger.warn("Name has basionym relation to a name that is not in the same homotypical group");
434
						}
435
					}else if (type.isReplacedSynonymRelation() && doReplacedSynonym)  {
436
						if (testRelatedNameInThisGroup(nameRel)){
437
							result.add(nameRel);
438
						}else{
439
							logger.warn("Name has replaced synonym relation to a name that is not in the same homotypical group");
440
						}
441
					}
442
				}
443
			}
444
		}
445
		return result;
446
	}
447

    
448
	private boolean testRelatedNameInThisGroup(NameRelationship nameRel){
449
		TaxonName toName = nameRel.getToName();
450
		return (this.getTypifiedNames().contains(toName));
451
	}
452

    
453
	private boolean isBasionymOrRepSynRel(NameRelationshipType relType){
454
		if (relType == null){
455
			throw new IllegalArgumentException("NameRelationshipType should never be null");
456
		}else if (relType.equals(NameRelationshipType.BASIONYM())) {
457
			return true;
458
		}else if (relType.equals(NameRelationshipType.REPLACED_SYNONYM())){
459
			return true;
460
		}else{
461
			return false;
462
		}
463
	}
464
}
(1-1/39)