fix problem with changeSynonymToAcceptedTaxon of a synonym being part of an homotypic...
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / taxon / Synonym.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.taxon;
11
12 import org.apache.log4j.Logger;
13 import org.hibernate.annotations.Cascade;
14 import org.hibernate.annotations.CascadeType;
15 import org.hibernate.envers.Audited;
16 import org.hibernate.search.annotations.Indexed;
17 import org.hibernate.validator.constraints.NotEmpty;
18 import org.springframework.beans.factory.annotation.Configurable;
19
20 import eu.etaxonomy.cdm.model.common.IRelated;
21 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
22 import eu.etaxonomy.cdm.model.reference.Reference;
23 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
24 import eu.etaxonomy.cdm.strategy.cache.taxon.TaxonBaseDefaultCacheStrategy;
25 import eu.etaxonomy.cdm.validation.Level2;
26
27 import java.util.*;
28
29 import javax.persistence.*;
30 import javax.validation.Valid;
31 import javax.validation.constraints.NotNull;
32 import javax.xml.bind.annotation.XmlAccessType;
33 import javax.xml.bind.annotation.XmlAccessorType;
34 import javax.xml.bind.annotation.XmlElement;
35 import javax.xml.bind.annotation.XmlElementWrapper;
36 import javax.xml.bind.annotation.XmlIDREF;
37 import javax.xml.bind.annotation.XmlRootElement;
38 import javax.xml.bind.annotation.XmlSchemaType;
39 import javax.xml.bind.annotation.XmlType;
40
41 /**
42 * The class for synonyms: these are {@link TaxonBase taxa} the {@link name.TaxonNameBase taxon names}
43 * of which are not used by the {@link TaxonBase#getSec() reference} to designate a real
44 * taxon but are mentioned as taxon names that were oder are used by some other
45 * unspecified references to designate (at least to some extent) the same
46 * particular real taxon. Synonyms that are involved in no
47 * {@link SynonymRelationship synonym relationship} are actually meaningless.<BR>
48 * Splitting taxa in "accepted/correct" and "synonyms"
49 * makes it easier to handle particular relationships between
50 * ("accepted/correct") {@link Taxon taxa} on the one hand and between ("synonym") taxa
51 * and ("accepted/correct") taxa on the other.
52 *
53 * @author m.doering
54 * @version 1.0
55 * @created 08-Nov-2007 13:06:55
56 */
57 @XmlAccessorType(XmlAccessType.FIELD)
58 @XmlType(name = "Synonym", propOrder = {
59 "synonymRelations"
60 })
61 @XmlRootElement(name = "Synonym")
62 @Entity
63 @Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonBase")
64 @Audited
65 @Configurable
66 public class Synonym extends TaxonBase<IIdentifiableEntityCacheStrategy<Synonym>> implements IRelated<SynonymRelationship>{
67 private static final long serialVersionUID = -454067515022159757L;
68
69 @SuppressWarnings("unused")
70 private static final Logger logger = Logger.getLogger(Synonym.class);
71
72 // Don't need the synonym relations here since they are stored at taxon side?
73 @XmlElementWrapper(name = "SynonymRelations")
74 @XmlElement(name = "SynonymRelationship")
75 @XmlIDREF
76 @XmlSchemaType(name = "IDREF")
77 @OneToMany(mappedBy="relatedFrom", fetch=FetchType.LAZY, orphanRemoval=true)
78 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
79 @NotNull
80 @NotEmpty(groups = Level2.class,message="{eu.etaxonomy.cdm.model.taxon.Synonym.noOrphanedSynonyms.message}")
81 @Valid
82 private Set<SynonymRelationship> synonymRelations = new HashSet<SynonymRelationship>();
83
84 // ************* CONSTRUCTORS *************/
85 /**
86 * Class constructor: creates a new empty synonym instance.
87 *
88 * @see #Synonym(TaxonNameBase, Reference)
89 */
90 //TODO should be private, but still produces Spring init errors
91 public Synonym(){
92 this.cacheStrategy = new TaxonBaseDefaultCacheStrategy<Synonym>();
93 }
94
95 /**
96 * Class constructor: creates a new synonym instance with
97 * the {@link eu.etaxonomy.cdm.model.name.TaxonNameBase taxon name} used and the {@link eu.etaxonomy.cdm.model.reference.Reference reference}
98 * using it as a synonym and not as an ("accepted/correct") {@link Taxon taxon}.
99 *
100 * @param taxonNameBase the taxon name used
101 * @param sec the reference using the taxon name
102 * @see Synonym#Synonym(TaxonNameBase, Reference)
103 */
104 public Synonym(TaxonNameBase taxonNameBase, Reference sec){
105 super(taxonNameBase, sec);
106 this.cacheStrategy = new TaxonBaseDefaultCacheStrategy<Synonym>();
107 }
108
109 //********* METHODS **************************************/
110
111 /**
112 * Creates a new synonym instance with
113 * the {@link eu.etaxonomy.cdm.model.name.TaxonNameBase taxon name} used and the {@link eu.etaxonomy.cdm.model.reference.Reference reference}
114 * using it as a synonym and not as an ("accepted/correct") {@link Taxon taxon}.
115 *
116 * @param taxonNameBase the taxon name used
117 * @param sec the reference using the taxon name
118 * @see #Synonym(TaxonNameBase, Reference)
119 */
120 public static Synonym NewInstance(TaxonNameBase taxonName, Reference sec){
121 Synonym result = new Synonym(taxonName, sec);
122 return result;
123 }
124
125 /**
126 * Returns the set of all {@link SynonymRelationship synonym relationships}
127 * in which <i>this</i> synonym is involved. <i>This</i> synonym can only
128 * be the source within these synonym relationships.
129 *
130 * @see #addSynonymRelation(SynonymRelationship)
131 * @see #addRelationship(SynonymRelationship)
132 * @see #removeSynonymRelation(SynonymRelationship)
133 */
134 public Set<SynonymRelationship> getSynonymRelations() {
135 if(synonymRelations == null) {
136 this.synonymRelations = new HashSet<SynonymRelationship>();
137 }
138 return synonymRelations;
139 }
140
141 /**
142 * @see #getSynonymRelations()
143 */
144 protected void setSynonymRelations(Set<SynonymRelationship> synonymRelations) {
145 this.synonymRelations = synonymRelations;
146 }
147
148 /**
149 * Adds an existing {@link SynonymRelationship synonym relationship} to the set of
150 * {@link #getSynonymRelations() synonym relationships} assigned to <i>this</i> synonym. If
151 * the source of the synonym relationship does not match with <i>this</i>
152 * synonym no addition will be carried out.<BR>
153 * This methods does the same as the {@link #addRelationship() addRelationship} method.
154 *
155 * @param synonymRelation the synonym relationship to be added to <i>this</i> synonym's
156 * synonym relationships set
157 * @see #addRelationship(SynonymRelationship)
158 * @see #getSynonymRelations()
159 * @see #removeSynonymRelation(SynonymRelationship)
160 */
161 protected void addSynonymRelation(SynonymRelationship synonymRelation) {
162 this.synonymRelations.add(synonymRelation);
163 }
164 /**
165 * Removes one element from the set of {@link SynonymRelationship synonym relationships} assigned
166 * to <i>this</i> synonym. Due to bidirectionality the given
167 * synonym relationship will also be removed from the set of synonym
168 * relationships assigned to the {@link Taxon#getSynonymRelations() taxon} involved in the
169 * relationship. Furthermore the content of
170 * the {@link SynonymRelationship#getAcceptedTaxon() accepted taxon} attribute and of the
171 * {@link SynonymRelationship#getSynonym() synonym} attribute within the synonym relationship
172 * itself will be set to "null".
173 *
174 * @param synonymRelation the synonym relationship which should be deleted
175 * @see #getSynonymRelations()
176 * @see #addRelationship(SynonymRelationship)
177 */
178 public void removeSynonymRelation(SynonymRelationship synonymRelation) {
179 synonymRelation.setSynonym(null);
180 Taxon taxon = synonymRelation.getAcceptedTaxon();
181 if (taxon != null){
182 synonymRelation.setAcceptedTaxon(null);
183 taxon.removeSynonymRelation(synonymRelation);
184 }
185 this.synonymRelations.remove(synonymRelation);
186 }
187
188
189 /**
190 * Adds an existing {@link SynonymRelationship synonym relationship} to the set of
191 * {@link #getSynonymRelations() synonym relationships} assigned to <i>this</i> synonym. If
192 * the source of the synonym relationship does not match with <i>this</i>
193 * synonym no addition will be carried out.<BR>
194 * This methods does the same as the {@link #addSynonymRelation(SynonymRelationship) addSynonymRelation} method.
195 *
196 * @param synonymRelation the synonym relationship to be added to <i>this</i> synonym's
197 * synonym relationships set
198 * @see #addSynonymRelation(SynonymRelationship)
199 * @see #getSynonymRelations()
200 * @see #removeSynonymRelation(SynonymRelationship)
201 */
202 /* (non-Javadoc)
203 * @see eu.etaxonomy.cdm.model.common.IRelated#addRelationship(eu.etaxonomy.cdm.model.common.RelationshipBase)
204 */
205 public void addRelationship(SynonymRelationship rel){
206 addSynonymRelation(rel);
207 }
208
209
210 /**
211 * Returns the set of all ("accepted/correct") {@link Taxon taxa} involved in the same
212 * {@link SynonymRelationship synonym relationships} as <i>this</i> synonym.
213 * Each taxon is the target and <i>this</i> synonym is the source of a {@link SynonymRelationship synonym relationship}
214 * belonging to the {@link #getSynonymRelations() set of synonym relationships} assigned to
215 * <i>this</i> synonym. For a particular synonym there are more than one
216 * ("accepted/correct") taxon only if the {@link SynonymRelationship#isProParte() "is pro parte" flag}
217 * of the corresponding {@link SynonymRelationship synonym relationships} is set.
218 *
219 * @see #getSynonymRelations()
220 * @see #getRelationType(Taxon)
221 * @see SynonymRelationship#isProParte()
222 */
223 @Transient
224 public Set<Taxon> getAcceptedTaxa() {
225 Set<Taxon>taxa=new HashSet<Taxon>();
226 for (SynonymRelationship rel:getSynonymRelations()){
227 taxa.add(rel.getAcceptedTaxon());
228 }
229 return taxa;
230 }
231
232 /**
233 * Returns true if <i>this</i> is a synonym of the given taxon.
234 *
235 * @param taxon the taxon to check synonym for
236 * @return true if <i>this</i> is a ynonms of the given taxon
237 *
238 * @see #getAcceptedTaxa()
239 */
240 @Transient
241 public boolean isSynonymOf(Taxon taxon){
242 return getAcceptedTaxa().contains(taxon);
243 }
244
245 @Transient
246 public boolean isOrphaned() {
247 return false;
248 }
249 /**
250 * Returns the set of {@link SynonymRelationshipType synonym relationship types} of the
251 * {@link SynonymRelationship synonym relationships} where the {@link SynonymRelationship#getSynonym() synonym}
252 * is <i>this</i> synonym and the {@link SynonymRelationship#getAcceptedTaxon() taxon}
253 * is the given one. "Null" is returned if the given taxon is "null" or if
254 * no synonym relationship exists from <i>this</i> synonym to the
255 * given taxon.
256 *
257 * @param taxon the ("accepted/correct") taxon which a synonym relationship
258 * from <i>this</i> synonym should point to
259 * @see #getSynonymRelations()
260 * @see #getAcceptedTaxa()
261 */
262 public Set<SynonymRelationshipType> getRelationType(Taxon taxon){
263 Set<SynonymRelationshipType> result = new HashSet<SynonymRelationshipType>();
264 if (taxon == null ){
265 return result;
266 }
267 for (SynonymRelationship rel : getSynonymRelations()){
268 Taxon acceptedTaxon = rel.getAcceptedTaxon();
269 if (taxon.equals(acceptedTaxon)){
270 result.add(rel.getType());
271 }
272 }
273 return result;
274 }
275
276 /**
277 * Replaces ALL accepted taxa of this synonym by the new accepted taxon.
278 * The citation information (citation /microcitation) of the synonym relationship
279 * is kept.
280 * @param newAcceptedTaxon
281 * the new accepted taxon
282 * @param relType
283 * if not <code>null</code> the relationship type is changed to relType
284 * @param copyCitationInfo
285 * if true the citation and the microcitation of relationship
286 * is not changed.
287 * @param citation
288 * if copyCitationInfo is <code>false</code> this citation is set
289 * to the synonym relationship.
290 * @param microCitation
291 * if copyCitationInfo is <code>false</code> this micro citation is set
292 * to the synonym relationship.
293
294 * @param acceptedTaxon
295 */
296 public void replaceAcceptedTaxon(Taxon newAcceptedTaxon, SynonymRelationshipType relType, boolean copyCitationInfo, Reference citation, String microCitation) {
297 Set<SynonymRelationship> rels = new HashSet<SynonymRelationship>();
298 rels.addAll(this.getSynonymRelations()); //avoid concurrent modification exception
299
300 for (SynonymRelationship rel : rels){
301 Taxon oldAcceptedTaxon = rel.getAcceptedTaxon();
302 Synonym syn = rel.getSynonym();
303 //remove old
304 oldAcceptedTaxon.removeSynonym(rel.getSynonym(), false);
305
306
307 SynonymRelationship newRel = (SynonymRelationship)rel.clone();
308 newRel.setAcceptedTaxon(newAcceptedTaxon);
309 newAcceptedTaxon.getSynonymRelations().add(newRel);
310 newRel.setSynonym(syn);
311 syn.addSynonymRelation(newRel);
312
313 newRel.setType(relType);
314 }
315 }
316 //*********************** CLONE ********************************************************/
317
318 @Override
319 public Object clone() {
320 Synonym result;
321 result = (Synonym)super.clone();
322
323 result.setSynonymRelations(new HashSet<SynonymRelationship>());
324
325 for (SynonymRelationship synRelationship : this.getSynonymRelations()){
326 SynonymRelationship newRelationship = (SynonymRelationship)synRelationship.clone();
327 newRelationship.setRelatedFrom(result);
328 result.synonymRelations.add(newRelationship);
329 }
330 return result;
331
332 }
333 }