Project

General

Profile

Download (13.8 KB) Statistics
| Branch: | Tag: | Revision:
1
// $Id$
2
/**
3
* Copyright (C) 2007 EDIT
4
* European Distributed Institute of Taxonomy 
5
* http://www.e-taxonomy.eu
6
* 
7
* The contents of this file are subject to the Mozilla Public License Version 1.1
8
* See LICENSE.TXT at the top of this package for the full license terms.
9
*/
10

    
11
package eu.etaxonomy.cdm.model.taxon;
12

    
13
import java.util.ArrayList;
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.ManyToOne;
21
import javax.persistence.OneToMany;
22
import javax.persistence.OneToOne;
23
import javax.persistence.Transient;
24
import javax.xml.bind.annotation.XmlAccessType;
25
import javax.xml.bind.annotation.XmlAccessorType;
26
import javax.xml.bind.annotation.XmlElement;
27
import javax.xml.bind.annotation.XmlElementWrapper;
28
import javax.xml.bind.annotation.XmlIDREF;
29
import javax.xml.bind.annotation.XmlRootElement;
30
import javax.xml.bind.annotation.XmlSchemaType;
31
import javax.xml.bind.annotation.XmlType;
32

    
33
import org.apache.log4j.Logger;
34
import org.hibernate.annotations.Cascade;
35
import org.hibernate.annotations.CascadeType;
36
import org.hibernate.envers.Audited;
37

    
38
import eu.etaxonomy.cdm.model.common.IReferencedEntity;
39
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
40
import eu.etaxonomy.cdm.model.common.Language;
41
import eu.etaxonomy.cdm.model.common.LanguageString;
42
import eu.etaxonomy.cdm.model.name.Rank;
43
import eu.etaxonomy.cdm.model.reference.ReferenceBase;
44

    
45
/**
46
 * @author a.mueller
47
 * @created 31.03.2009
48
 * @version 1.0
49
 */
50
@XmlAccessorType(XmlAccessType.FIELD)
51
@XmlType(name = "TaxonomicTree", propOrder = {
52
    "name",
53
    "rootNodes",
54
    "reference",
55
    "microReference"
56
})
57
@XmlRootElement(name = "TaxonomicTree")
58
@Entity
59
@Audited
60
public class TaxonomicTree extends IdentifiableEntity implements IReferencedEntity{
61
	private static final long serialVersionUID = -753804821474209635L;
62
	private static final Logger logger = Logger.getLogger(TaxonomicTree.class);
63
	
64
	@XmlElement(name = "name")
65
	@XmlIDREF
66
	@XmlSchemaType(name = "IDREF")
67
	@OneToOne(fetch = FetchType.LAZY)
68
	@Cascade({CascadeType.SAVE_UPDATE})
69
	private LanguageString name;
70
	
71
//	@XmlElementWrapper(name = "allNodes")
72
//	@XmlElement(name = "taxonNode")
73
//    @XmlIDREF
74
//    @XmlSchemaType(name = "IDREF")
75
//    @OneToMany(mappedBy="taxonomicTree", fetch=FetchType.LAZY)
76
//    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
77
//    @Deprecated // FIXME remove this. A set containing all nodes of the tree is a major performance killer, especially when it is not really in use
78
//	private Set<TaxonNode> allNodes = new HashSet<TaxonNode>();
79

    
80
	@XmlElementWrapper(name = "rootNodes")
81
	@XmlElement(name = "rootNode")
82
    @XmlIDREF
83
    @XmlSchemaType(name = "IDREF")
84
    @OneToMany(fetch=FetchType.LAZY)
85
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
86
	private Set<TaxonNode> rootNodes = new HashSet<TaxonNode>();
87

    
88
	@XmlElement(name = "reference")
89
	@XmlIDREF
90
	@XmlSchemaType(name = "IDREF")
91
	@ManyToOne(fetch = FetchType.LAZY)
92
	@Cascade({CascadeType.SAVE_UPDATE})
93
	private ReferenceBase reference;
94
	
95
	@XmlElement(name = "microReference")
96
	private String microReference;
97
	
98
//	/**
99
//	 * If this taxonomic view is an alternative view for a subtree in an other view(parent view),
100
//	 * the alternativeViewRoot is the connection node from this view to the parent view.
101
//	 * It replaces another node in the parent view.
102
//	 */
103
//	private AlternativeViewRoot alternativeViewRoot;
104
	
105
	
106
	public static TaxonomicTree NewInstance(String name){
107
		return NewInstance(name, Language.DEFAULT());
108
	}
109
	
110
	public static TaxonomicTree NewInstance(String name, Language language){
111
		TaxonomicTree result =  new TaxonomicTree();
112
		LanguageString langName = LanguageString.NewInstance(name, language);
113
		result.setName(langName);
114
		return result;
115
	}
116
	
117
	protected TaxonomicTree(){
118
		super();
119
	}
120
	
121
	
122
	
123
	/**
124
	 * Adds a taxon to the taxonomic tree and makes it one of the root nodes.
125
	 * @param taxon
126
	 * @param synonymUsed
127
	 * @return
128
	 */
129
	public TaxonNode addRoot(Taxon taxon, Synonym synonymUsed){
130
		TaxonNode newRoot = new TaxonNode(taxon, this);
131
		rootNodes.add(newRoot);
132
		newRoot.setParent(null);
133
		newRoot.setTaxonomicView(this);
134
		newRoot.setTaxon(taxon);
135
//		newRoot.setReferenceForParentChildRelation(ref); //ref not needed for root !!
136
		newRoot.setSynonymToBeUsed(synonymUsed);
137
		return newRoot;
138
	}
139
	
140
	public boolean removeRoot(TaxonNode node){
141
		boolean result = false;
142
		if(node.isRootNode()){
143

    
144
			for (TaxonNode childNode : node.getChildNodes()){
145
				node.removeChild(childNode);
146
			}
147
			result = rootNodes.remove(node);
148

    
149
			node.getTaxon().removeTaxonNode(node);
150
			node.setParent(null);
151
			node.setTaxonomicView(null);
152
			node.setTaxon(null);			
153
		}
154
		return result;
155
	}
156
	
157
	/**
158
	 * Appends an existing root node to another node of this tree. The existing root node becomes 
159
	 * an ordinary node.
160
	 * @param root
161
	 * @param otherNode
162
	 * @param ref
163
	 * @param microReference
164
	 * @throws IllegalArgumentException
165
	 */
166
	public void makeRootChildOfOtherNode(TaxonNode root, TaxonNode otherNode, ReferenceBase ref, String microReference)
167
				throws IllegalArgumentException{
168
		if (otherNode == null){
169
			throw new NullPointerException("other node must not be null");
170
		}
171
		if (! getRootNodes().contains(root)){
172
			throw new IllegalArgumentException("root node to be added as child must already be root node within this tree");
173
		}
174
		if (otherNode.getTaxonomicTree() == null || ! otherNode.getTaxonomicTree().equals(this)){
175
			throw new IllegalArgumentException("other node must already be node within this tree");
176
		}
177
		if (otherNode.equals(root)){
178
			throw new IllegalArgumentException("root node and other node must not be the same");
179
		}
180
		otherNode.addChildNote(root, ref, microReference, null);
181
		getRootNodes().remove(root);
182
	}
183
	
184
//	public void makeThisNodePartOfOtherView(TaxonNode oldRoot, TaxonNode replacedNodeInOtherView, ReferenceBase reference, String microReference){
185
//		AlternativeViewRoot newRoot = new AlternativeViewRoot(oldRoot, replacedNodeInOtherView, reference, microReference);
186
//	}
187
	
188

    
189
	/**
190
	 * Checks if the given taxon is part of <b>this</b> tree.
191
	 * @param taxon
192
	 * @return
193
	 */
194
	public boolean isTaxonInTree(Taxon taxon){
195
		return (getNode(taxon) != null);
196
	}
197
	
198
	/**
199
	 * Checks if the given taxon is part of <b>this</b> tree. If so the according TaxonNode is returned.
200
	 * Otherwise null is returned.
201
	 * @param taxon
202
	 * @return
203
	 */
204
	public TaxonNode getNode(Taxon taxon){
205
		if (taxon == null){
206
			return null;
207
		}
208
		for (TaxonNode taxonNode: taxon.getTaxonNodes()){
209
			if (taxonNode.getTaxonomicTree().equals(this)){
210
				return taxonNode;
211
			}
212
		}
213
		return null;
214
	}
215
	
216
	/**
217
	 * Checks if the given taxon is one of the root taxa in <b>this</b> tree.
218
	 * @param taxon
219
	 * @return
220
	 */
221
	public boolean isRootInTree(Taxon taxon){
222
		return (getRootNode(taxon) != null);
223
	}
224
	
225
	/**
226
	 * Checks if the taxon is a root taxon in <b>this</b> tree and returns the according node if true.
227
	 * Returns null otherwise.
228
	 * @param taxon
229
	 * @return
230
	 */
231
	public TaxonNode getRootNode(Taxon taxon){
232
		if (taxon == null){
233
			return null;
234
		}
235
		for (TaxonNode taxonNode: taxon.getTaxonNodes()){
236
			if (taxonNode.getTaxonomicTree().equals(this)){
237
				if (this.getRootNodes().contains(taxonNode)){
238
					if (taxonNode.getParent() != null){
239
						logger.warn("A root node should not have parent");
240
					}
241
					return taxonNode;
242
				}
243
			}
244
		}
245
		return null;
246
	}
247

    
248
	private boolean handleCitationOverwrite(TaxonNode childNode, ReferenceBase citation, String microCitation){
249
		if (citation != null){
250
			if (childNode.getReferenceForParentChildRelation() != null && ! childNode.getReferenceForParentChildRelation().equals(citation)){
251
				logger.warn("ReferenceForParentChildRelation will be overwritten");
252
			}
253
			childNode.setReferenceForParentChildRelation(citation);
254
		}
255
		if (microCitation != null){
256
			if (childNode.getMicroReferenceForParentChildRelation() != null && ! childNode.getMicroReferenceForParentChildRelation().equals(microCitation)){
257
				logger.warn("MicroReferenceForParentChildRelation will be overwritten");
258
			}
259
			childNode.setMicroReferenceForParentChildRelation(microCitation);
260
		}
261
		return true;
262
	}
263
	
264
	
265
	/**
266
	 * Relates two taxa as parent-child nodes within a taxonomic tree. <BR>
267
	 * If the taxa are not yet part of the tree they are added to it.<Br>
268
	 * If the child taxon is a root still it is added as child and deleted from the rootNode set.<Br>
269
	 * If the child is a child of another parent already an IllegalStateException is thrown because a child can have only 
270
	 * one parent. <Br>
271
	 * If the parent-child relationship between these two taxa already exists nothing is changed. Only 
272
	 * citation and microcitation are overwritten by the new values if these values are not null.
273
	 * @param parent
274
	 * @param child
275
	 * @param citation
276
	 * @param microCitation
277
	 * @return
278
	 * @throws IllegalStateException If the child is a child of another parent already
279
	 */
280
	public boolean addParentChild (Taxon parent, Taxon child, ReferenceBase citation, String microCitation)
281
			throws IllegalStateException{
282
		try {
283
			TaxonNode parentNode = this.getNode(parent);
284
			TaxonNode childNode = this.getNode(child);
285
			
286
			//if child exists in tree and has a parent 
287
			//no multiple parents are allowed in the tree
288
			if (childNode != null && childNode.getParent() != null){
289
				//...different to the parent taxon  throw exception
290
				if (! childNode.getParent().getTaxon().equals(parent) ){
291
					throw new IllegalStateException("The child taxon is already part of the tree but has an other parent taxon than the one than the parent to be added. Child: " + child.toString() + ", new parent:" + parent.toString() + ", old parent: " + childNode.getParent().getTaxon().toString()) ;
292
				//... same as the parent taxon do nothing but overwriting citation and microCitation
293
				}else{
294
					handleCitationOverwrite(childNode, citation, microCitation);
295
					return true;
296
				}
297
			}
298
			
299
			//add parent node if not exist
300
			if (parentNode == null){
301
				parentNode = this.addRoot(parent, null);
302
			}
303
			
304
			//add child if not exists
305
			if (childNode == null){
306
				parentNode.addChild(child, citation, microCitation);
307
			}else{
308
				//child is still root
309
				//TODO test if child is rootNode otherwise thrwo IllegalStateException
310
				if (! this.isRootInTree(child)){
311
					throw new IllegalStateException("Child is not a root but must be");
312
				}
313
				this.makeRootChildOfOtherNode(childNode, parentNode, citation, microCitation);
314
			}
315
		} catch (IllegalStateException e) {
316
			throw e;
317
		} catch (RuntimeException e){
318
			throw e;
319
		}
320
		return true;
321
	}
322
	
323
	
324
	@Transient
325
	public ReferenceBase getCitation() {
326
		return reference;
327
	}
328
	
329
	public LanguageString getName() {
330
		return name;
331
	}
332

    
333
	public void setName(LanguageString name) {
334
		this.name = name;
335
	}
336

    
337
	/**
338
	 * Returns a set containing all nodes in this taxonomic tree.
339
	 * 
340
	 * Caution: Use this method with care. It can be very time and resource consuming and might
341
	 * run into OutOfMemoryExceptions for big trees. 
342
	 * 
343
	 * @return
344
	 */
345
	@Transient
346
	@Deprecated
347
	public Set<TaxonNode> getAllNodes() {
348
		Set<TaxonNode> allNodes = new HashSet<TaxonNode>();
349
		
350
		for(TaxonNode rootNode : getRootNodes()){
351
			allNodes.addAll(rootNode.getAllNodes());
352
		}
353
		
354
		return allNodes;
355
	}	
356
	
357
	public Set<TaxonNode> getRootNodes() {
358
		return rootNodes;
359
	}
360

    
361
	public void setRootNodes(Set<TaxonNode> rootNodes) {
362
		this.rootNodes = rootNodes;
363
	}
364

    
365
	public ReferenceBase getReference() {
366
		return reference;
367
	}
368

    
369
	public void setReference(ReferenceBase reference) {
370
		this.reference = reference;
371
	}
372
	
373

    
374
	/**
375
	 * @return the microReference
376
	 */
377
	public String getMicroReference() {
378
		return microReference;
379
	}
380

    
381
	/**
382
	 * @param microReference the microReference to set
383
	 */
384
	public void setMicroReference(String microReference) {
385
		this.microReference = microReference;
386
	}
387

    
388
	/* (non-Javadoc)
389
	 * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#generateTitle()
390
	 */
391
	@Override
392
	public String generateTitle() {
393
		return name.getText();
394
	}
395

    
396
	public int compareTo(Object o) {
397
		return 0;
398
	}
399
	
400
	/**
401
	 * Returns all TaxonNodes of the tree for a given Rank.
402
	 * If a branch does not contain a TaxonNode with a TaxonName at the given
403
	 * Rank the node associated with the next lower Rank is taken as root node.
404
	 * If the <code>rank</code> is null the absolute root nodes will be returned.
405
	 * 
406
	 * @param rank may be null
407
	 * @return
408
	 */
409
	public List<TaxonNode> getRankSpecificRootNodes(Rank rank) {
410
		List<TaxonNode> baseNodes = new ArrayList<TaxonNode>();
411
		if(rank != null){
412
			findNodesForRank(rank, getRootNodes(), baseNodes);
413
		} else {
414
			baseNodes.addAll(getRootNodes());
415
		}
416
		return baseNodes;
417
	}
418

    
419
	/**
420
	 * Walks the tree returning all nodes of exactly the given <code>rank</code>
421
	 * or the next lower rank if an exact match by rank is not possible.
422
	 * 
423
	 * @param baseRank
424
	 *            the rank to return nodes for
425
	 * @param nodeSet
426
	 *            the set of nodes to search in
427
	 * @param rootNodes
428
	 *            a List to put the found nodes in
429
	 * @return the <code>rootNodes</code> List given as parameter
430
	 */
431
	private List<TaxonNode> findNodesForRank(Rank baseRank, Set<TaxonNode> nodeSet, List<TaxonNode> rootNodes) {
432
		for(TaxonNode node : nodeSet){
433
			Rank thisRank = node.getTaxon().getName().getRank();
434
			if(thisRank.isHigher(baseRank)){
435
				// if the current rank still is higher than the given baseRank iterate deeper into tree
436
				findNodesForRank(baseRank, node.getChildNodes(), rootNodes);
437
			} else {
438
				// gotsha! It is a base node of this level
439
				rootNodes.add(node);
440
			}
441
		}
442
		return rootNodes;
443
	}
444

    
445
}
(11-11/13)