Project

General

Profile

Download (34.6 KB) Statistics
| Branch: | Tag: | Revision:
1 f37d249f Andreas Müller
/**
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 java.util.ArrayList;
13 45c1a780 Andreas Müller
import java.util.HashMap;
14 f37d249f Andreas Müller
import java.util.HashSet;
15
import java.util.List;
16 45c1a780 Andreas Müller
import java.util.Map;
17 f37d249f Andreas Müller
import java.util.Set;
18
19 974e7f9a Andreas Müller
import javax.persistence.Column;
20 f37d249f Andreas Müller
import javax.persistence.Entity;
21
import javax.persistence.FetchType;
22 45c1a780 Andreas Müller
import javax.persistence.JoinTable;
23 f37d249f Andreas Müller
import javax.persistence.ManyToOne;
24 45c1a780 Andreas Müller
import javax.persistence.MapKeyJoinColumn;
25 f37d249f Andreas Müller
import javax.persistence.OneToMany;
26
import javax.persistence.OrderBy;
27
import javax.persistence.OrderColumn;
28
import javax.persistence.Transient;
29
import javax.xml.bind.annotation.XmlAccessType;
30
import javax.xml.bind.annotation.XmlAccessorType;
31 98053545 Andreas Müller
import javax.xml.bind.annotation.XmlAttribute;
32 f37d249f Andreas Müller
import javax.xml.bind.annotation.XmlElement;
33
import javax.xml.bind.annotation.XmlElementWrapper;
34
import javax.xml.bind.annotation.XmlIDREF;
35
import javax.xml.bind.annotation.XmlRootElement;
36
import javax.xml.bind.annotation.XmlSchemaType;
37
import javax.xml.bind.annotation.XmlType;
38 45c1a780 Andreas Müller
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
39 f37d249f Andreas Müller
40
import org.apache.log4j.Logger;
41 d9c82be9 Andreas Kohlbecker
import org.hibernate.LazyInitializationException;
42 f37d249f Andreas Müller
import org.hibernate.annotations.Cascade;
43
import org.hibernate.annotations.CascadeType;
44
import org.hibernate.annotations.Index;
45
import org.hibernate.annotations.Table;
46
import org.hibernate.envers.Audited;
47
import org.hibernate.search.annotations.ContainedIn;
48
import org.hibernate.search.annotations.IndexedEmbedded;
49
50 23e98ce3 Andreas Kohlbecker
import eu.etaxonomy.cdm.hibernate.HHH_9751_Util;
51 f37d249f Andreas Müller
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
52 45c1a780 Andreas Müller
import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
53 f37d249f Andreas Müller
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
54
import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
55
import eu.etaxonomy.cdm.model.common.CdmBase;
56
import eu.etaxonomy.cdm.model.common.DefinedTerm;
57
import eu.etaxonomy.cdm.model.common.ITreeNode;
58 45c1a780 Andreas Müller
import eu.etaxonomy.cdm.model.common.Language;
59
import eu.etaxonomy.cdm.model.common.LanguageString;
60 85b59453 Andreas Müller
import eu.etaxonomy.cdm.model.common.MultilanguageText;
61 f37d249f Andreas Müller
import eu.etaxonomy.cdm.model.name.Rank;
62
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
63
import eu.etaxonomy.cdm.model.reference.Reference;
64
import eu.etaxonomy.cdm.validation.Level3;
65
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustBeLowerRankThanParent;
66
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustDeriveNameFromParent;
67
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustNotSkipRanks;
68
69
/**
70
 * @author a.mueller
71
 * @created 31.03.2009
72
 */
73
@XmlAccessorType(XmlAccessType.FIELD)
74
@XmlType(name = "TaxonNode", propOrder = {
75
    "classification",
76
    "taxon",
77
    "parent",
78
    "treeIndex",
79
    "sortIndex",
80
    "childNodes",
81
    "referenceForParentChildRelation",
82
    "microReferenceForParentChildRelation",
83
    "countChildren",
84
    "agentRelations",
85 45c1a780 Andreas Müller
    "synonymToBeUsed",
86
    "excludedNote"
87 f37d249f Andreas Müller
})
88
@XmlRootElement(name = "TaxonNode")
89
@Entity
90 c70a7f0f Andreas Kohlbecker
//@Indexed disabled to reduce clutter in indexes, since this type is not used by any search
91
//@Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonNode")
92 f37d249f Andreas Müller
@Audited
93
@Table(appliesTo="TaxonNode", indexes = { @Index(name = "taxonNodeTreeIndex", columnNames = { "treeIndex" }) })
94
@ChildTaxaMustBeLowerRankThanParent(groups = Level3.class)
95
@ChildTaxaMustNotSkipRanks(groups = Level3.class)
96
@ChildTaxaMustDeriveNameFromParent(groups = Level3.class)
97
public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITreeNode<TaxonNode>, Cloneable{
98
    private static final long serialVersionUID = -4743289894926587693L;
99
    private static final Logger logger = Logger.getLogger(TaxonNode.class);
100
101
    @XmlElement(name = "taxon")
102
    @XmlIDREF
103
    @XmlSchemaType(name = "IDREF")
104
    @ManyToOne(fetch = FetchType.LAZY)
105
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
106
    @ContainedIn
107
    private Taxon taxon;
108
109
110
    @XmlElement(name = "parent")
111
    @XmlIDREF
112
    @XmlSchemaType(name = "IDREF")
113
    @ManyToOne(fetch = FetchType.LAZY)
114
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
115
    private TaxonNode parent;
116
117
118
    @XmlElement(name = "treeIndex")
119 974e7f9a Andreas Müller
    @Column(length=255)
120 f37d249f Andreas Müller
    private String treeIndex;
121
122
123
    @XmlElement(name = "classification")
124
    @XmlIDREF
125
    @XmlSchemaType(name = "IDREF")
126
    @ManyToOne(fetch = FetchType.LAZY)
127
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
128
//	TODO @NotNull // avoids creating a UNIQUE key for this field
129 2a5361d0 Andreas Kohlbecker
    @IndexedEmbedded(includeEmbeddedObjectId=true)
130 f37d249f Andreas Müller
    private Classification classification;
131
132
    @XmlElementWrapper(name = "childNodes")
133
    @XmlElement(name = "childNode")
134
    @XmlIDREF
135
    @XmlSchemaType(name = "IDREF")
136
    //see https://dev.e-taxonomy.eu/trac/ticket/3722
137
    @OrderColumn(name="sortIndex")
138
    @OrderBy("sortIndex")
139
    @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
140 08a698af Katja Luther
    //@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
141 45c1a780 Andreas Müller
    private List<TaxonNode> childNodes = new ArrayList<>();
142 f37d249f Andreas Müller
143
    //see https://dev.e-taxonomy.eu/trac/ticket/3722
144
    //see https://dev.e-taxonomy.eu/trac/ticket/4200
145
    private Integer sortIndex = -1;
146
147
	@XmlElement(name = "reference")
148
    @XmlIDREF
149
    @XmlSchemaType(name = "IDREF")
150
    @ManyToOne(fetch = FetchType.LAZY)
151
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
152 90046bc4 Andreas Müller
    private Reference referenceForParentChildRelation;
153 f37d249f Andreas Müller
154
    @XmlElement(name = "microReference")
155
    private String microReferenceForParentChildRelation;
156
157
    @XmlElement(name = "countChildren")
158
    private int countChildren;
159
160
    @XmlElementWrapper(name = "agentRelations")
161
    @XmlElement(name = "agentRelation")
162
    @XmlIDREF
163
    @XmlSchemaType(name = "IDREF")
164
    @OneToMany(mappedBy="taxonNode", fetch=FetchType.LAZY)
165
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
166 45c1a780 Andreas Müller
    private Set<TaxonNodeAgentRelation> agentRelations = new HashSet<>();
167 f37d249f Andreas Müller
168 98053545 Andreas Müller
    @XmlAttribute(name= "unplaced")
169
    private boolean unplaced = false;
170
    public boolean isUnplaced() {return unplaced;}
171
    public void setUnplaced(boolean unplaced) {this.unplaced = unplaced;}
172
173
    @XmlAttribute(name= "excluded")
174
    private boolean excluded = false;
175
    public boolean isExcluded() {return excluded;}
176
    public void setExcluded(boolean excluded) {this.excluded = excluded;}
177
178 45c1a780 Andreas Müller
    @XmlElement(name = "excludedNote")
179
    @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
180
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
181
    @MapKeyJoinColumn(name="excludedNote_mapkey_id")
182
    @JoinTable(name = "TaxonNode_ExcludedNote")  //to make possible to add also unplacedNote
183
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
184
    private Map<Language,LanguageString> excludedNote = new HashMap<>();
185 f37d249f Andreas Müller
186
//	private Taxon originalConcept;
187
//	//or
188
    @XmlElement(name = "synonymToBeUsed")
189
    @XmlIDREF
190
    @XmlSchemaType(name = "IDREF")
191
    @ManyToOne(fetch = FetchType.LAZY)
192
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
193
    private Synonym synonymToBeUsed;
194
195
// ******************** CONSTRUCTOR **********************************************/
196
197
    protected TaxonNode(){super();}
198
199
    /**
200
     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
201
     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
202
     * @param taxon
203
     * @param classification
204
     * @deprecated setting of classification is handled in the addTaxonNode() method,
205
     * use TaxonNode(taxon) instead
206
     */
207
    @Deprecated
208
    protected TaxonNode (Taxon taxon, Classification classification){
209
        this(taxon);
210
        setClassification(classification);
211
    }
212
213
    /**
214
     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
215
     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
216
     *
217
     * @param taxon
218
     */
219
    protected TaxonNode(Taxon taxon){
220
        setTaxon(taxon);
221
    }
222
223
// ************************* GETTER / SETTER *******************************/
224
225 712ad8f3 Andreas Kohlbecker
    @Transient
226 f37d249f Andreas Müller
    public Integer getSortIndex() {
227 541f08b0 Katja Luther
        return sortIndex;
228 f37d249f Andreas Müller
	}
229
    /**
230
     * SortIndex shall be handled only internally, therefore not public.
231
     * However, as javaassist only supports protected methods it needs to be protected, not private.
232
     * Alternatively we could use deproxy on every call of this method (see commented code)
233
     * @param i
234
     * @return
235
     * @deprecated for internal use only
236
     */
237
     @Deprecated
238
    protected void setSortIndex(Integer i) {
239
//    	 CdmBase.deproxy(this, TaxonNode.class).sortIndex = i;  //alternative solution for private, DON'T remove
240
    	 sortIndex = i;
241
	}
242
243
244
    public Taxon getTaxon() {
245
        return taxon;
246
    }
247
    protected void setTaxon(Taxon taxon) {
248
        this.taxon = taxon;
249
        if (taxon != null){
250
            taxon.addTaxonNode(this);
251
        }
252
    }
253
254
255
    @Override
256
    public List<TaxonNode> getChildNodes() {
257
        return childNodes;
258
    }
259
	protected void setChildNodes(List<TaxonNode> childNodes) {
260
		this.childNodes = childNodes;
261
	}
262
263
264
    public Classification getClassification() {
265
        return classification;
266
    }
267
    /**
268
     * THIS METHOD SHOULD NOT BE CALLED!
269
     * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
270
     * @param classification
271
     * @deprecated for internal use only
272
     */
273
    @Deprecated
274
    protected void setClassification(Classification classification) {
275
        this.classification = classification;
276
    }
277
278
279
    @Override
280
    public String getMicroReference() {
281
        return microReferenceForParentChildRelation;
282
    }
283
    public void setMicroReference(String microReference) {
284
        this.microReferenceForParentChildRelation = microReference;
285
    }
286
287
288
    @Override
289
    public Reference getReference() {
290
        return referenceForParentChildRelation;
291
    }
292
    public void setReference(Reference reference) {
293
        this.referenceForParentChildRelation = reference;
294
    }
295
296
    //countChildren
297
    public int getCountChildren() {
298
        return countChildren;
299
    }
300
    /**
301
     * @deprecated for internal use only
302
     * @param countChildren
303
     */
304
    @Deprecated
305
    protected void setCountChildren(int countChildren) {
306
        this.countChildren = countChildren;
307
    }
308
309
310
    //parent
311
    @Override
312
    public TaxonNode getParent(){
313
        return parent;
314
    }
315
    /**
316
     * Sets the parent of this taxon node.<BR>
317
     *
318
     * In most cases you would want to call setParentTreeNode(ITreeNode) which
319
     * handles updating of the bidirectional relationship
320
     *
321
     * @see setParentTreeNode(ITreeNode)
322
     * @param parent
323
     *
324
     */
325
    protected void setParent(TaxonNode parent) {
326
        this.parent = parent;
327
    }
328
329 f83def50 Andreas Müller
    // *************** Excluded Note ***************
330 85b59453 Andreas Müller
331
    /**
332
     * Returns the {@link MultilanguageText multi-language text} to add a note to the
333
     * excluded flag. The different {@link LanguageString language strings}
334
     * contained in the multi-language text should all have the same meaning.
335
     * @see #getExcludedNote()
336
     * @see #putExcludedNote(Language, String)
337
     */
338
    public Map<Language,LanguageString> getExcludedNote(){
339
        return this.excludedNote;
340
    }
341
342
    /**
343
     * Returns the excluded note string in the given {@link Language language}
344
     *
345
     * @param language  the language in which the description string looked for is formulated
346
     * @see             #getExcludedNote()
347
     * @see             #putExcludedNote(Language, String)
348
     */
349
    public String getExcludedNote(Language language){
350
        LanguageString languageString = excludedNote.get(language);
351
        if (languageString == null){
352
            return null;
353
        }else{
354
            return languageString.getText();
355
        }
356
    }
357
358
    /**
359
     * Adds a translated {@link LanguageString text in a particular language}
360
     * to the {@link MultilanguageText multilanguage text} used to add a note to
361
     * the {@link #isExcluded() excluded} flag.
362
     *
363
     * @param excludedNote   the language string adding a note to the excluded flag
364
     *                      in a particular language
365
     * @see                 #getExcludedNote()
366
     * @see                 #putExcludedNote(String, Language)
367
     */
368
    public void putExcludedNote(LanguageString excludedNote){
369 f83def50 Andreas Müller
        this.excludedNote.put(excludedNote.getLanguage(), excludedNote);
370 85b59453 Andreas Müller
    }
371
    /**
372
     * Creates a {@link LanguageString language string} based on the given text string
373
     * and the given {@link Language language} and adds it to the {@link MultilanguageText
374
     * multi-language text} used to annotate the excluded flag.
375
     *
376
     * @param text      the string annotating the excluded flag
377
     *                  in a particular language
378
     * @param language  the language in which the text string is formulated
379
     * @see             #getExcludedNote()
380
     * @see             #putExcludedNote(LanguageString)
381
     * @see             #removeExcludedNote(Language)
382
     */
383
    public void putExcludedNote(Language language, String text){
384
        this.excludedNote.put(language, LanguageString.NewInstance(text, language));
385
    }
386
387
    /**
388
     * Removes from the {@link MultilanguageText multilanguage text} used to annotate
389
     * the excluded flag the one {@link LanguageString language string}
390
     * with the given {@link Language language}.
391
     *
392
     * @param  lang the language in which the language string to be removed
393
     *       has been formulated
394
     * @see         #getExcludedNote()
395
     */
396
    public void removeExcludedNote(Language lang){
397
        this.excludedNote.remove(lang);
398
    }
399
400 f37d249f Andreas Müller
// ****************** Agent Relations ****************************/
401
402
403
    /**
404
     * @return
405
     */
406
    public Set<TaxonNodeAgentRelation> getAgentRelations() {
407
        return this.agentRelations;
408
    }
409
410
    public TaxonNodeAgentRelation addAgentRelation(DefinedTerm type, TeamOrPersonBase<?> agent){
411
        TaxonNodeAgentRelation result = TaxonNodeAgentRelation.NewInstance(this, agent, type);
412
        return result;
413
    }
414
    /**
415
     * @param nodeAgentRelation
416
     */
417
    protected void addAgentRelation(TaxonNodeAgentRelation agentRelation) {
418
        agentRelation.setTaxonNode(this);
419
        this.agentRelations.add(agentRelation);
420
    }
421
422
    /**
423
     * @param nodeAgentRelation
424
     */
425
    public void removeNodeAgent(TaxonNodeAgentRelation agentRelation) {
426
        agentRelation.setTaxonNode(this);
427
        agentRelations.remove(agentRelation);
428
    }
429
430
//********************
431
432
    //synonymToBeused
433
    public Synonym getSynonymToBeUsed() {
434
        return synonymToBeUsed;
435
    }
436
    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
437
        this.synonymToBeUsed = synonymToBeUsed;
438
    }
439
440
441
    //treeindex
442
    @Override
443
    public String treeIndex() {
444
        return treeIndex;
445
    }
446
    @Override
447
    @Deprecated //for CDM lib internal use only, may be removed in future versions
448
    public void setTreeIndex(String treeIndex) {
449
        this.treeIndex = treeIndex;
450
    }
451
452
453
454
//************************ METHODS **************************/
455
456
   @Override
457
    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
458
        return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
459
    }
460
461
462
    @Override
463
    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
464 b37deaa3 Katja Luther
        Classification classification = HibernateProxyHelper.deproxy(this.getClassification(), Classification.class);
465 c5aa066b Katja Luther
        taxon = HibernateProxyHelper.deproxy(taxon, Taxon.class);
466 d3c53a58 Katja Luther
        if (classification.isTaxonInTree(taxon)){
467 f37d249f Andreas Müller
            throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));
468
       }
469
       return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
470
    }
471
472
    /**
473
     * Moves a taxon node to a new parent. Descendents of the node are moved as well
474
     *
475
     * @param childNode the taxon node to be moved to the new parent
476
     * @return the child node in the state of having a new parent
477
     */
478
    @Override
479
    public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference){
480
        addChildNode(childNode, childNodes.size(), reference, microReference);
481
        return childNode;
482
    }
483
484
    /**
485
     * Inserts the given taxon node in the list of children of <i>this</i> taxon node
486
     * at the given (index + 1) position. If the given index is out of bounds
487
     * an exception will arise.<BR>
488
     * Due to bidirectionality this method must also assign <i>this</i> taxon node
489
     * as the parent of the given child.
490
     *
491
     * @param	child	the taxon node to be added
492
     * @param	index	the integer indicating the position at which the child
493
     * 					should be added
494
     * @see				#getChildNodes()
495
     * @see				#addChildNode(TaxonNode, Reference, String, Synonym)
496
     * @see				#deleteChildNode(TaxonNode)
497
     * @see				#deleteChildNode(int)
498
     */
499
    @Override
500
    public TaxonNode addChildNode(TaxonNode child, int index, Reference reference, String microReference){
501
        if (index < 0 || index > childNodes.size() + 1){
502
            throw new IndexOutOfBoundsException("Wrong index: " + index);
503
        }
504
           // check if this node is a descendant of the childNode
505
        if(child.getParent() != this && child.isAncestor(this)){
506
            throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
507
        }
508
509
        child.setParentTreeNode(this, index);
510
511
        child.setReference(reference);
512
        child.setMicroReference(microReference);
513
514
        return child;
515
    }
516
517
    /**
518
     * Sets this nodes classification. Updates classification of child nodes recursively.
519
     *
520
     * If the former and the actual tree are equal() this method does nothing.
521
     *
522
     * @throws IllegalArgumentException if newClassifciation is null
523
     *
524
     * @param newClassification
525
     */
526
    @Transient
527
    private void setClassificationRecursively(Classification newClassification) {
528
        if (newClassification == null){
529
        	throw new IllegalArgumentException("New Classification must not be 'null' when setting new classification.");
530
        }
531
    	if(! newClassification.equals(this.getClassification())){
532
            this.setClassification(newClassification);
533
            for(TaxonNode childNode : this.getChildNodes()){
534
                childNode.setClassificationRecursively(newClassification);
535
            }
536
        }
537
    }
538
539
    @Override
540
    public boolean deleteChildNode(TaxonNode node) {
541
        boolean result = removeChildNode(node);
542 08a698af Katja Luther
        Taxon taxon = HibernateProxyHelper.deproxy(node.getTaxon(), Taxon.class);
543
        node = HibernateProxyHelper.deproxy(node, TaxonNode.class);
544 f37d249f Andreas Müller
        node.setTaxon(null);
545 08a698af Katja Luther
546 f37d249f Andreas Müller
547
        ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
548
        for(TaxonNode childNode : childNodes){
549 08a698af Katja Luther
            HibernateProxyHelper.deproxy(childNode, TaxonNode.class);
550 f37d249f Andreas Müller
            node.deleteChildNode(childNode);
551
        }
552 08a698af Katja Luther
        taxon.removeTaxonNode(node);
553 f37d249f Andreas Müller
        return result;
554
    }
555
556
    /**
557
     * Deletes the child node and also removes children of childnode
558
     * recursively if delete children is <code>true</code>
559
     * @param node
560
     * @param deleteChildren
561
     * @return
562
     */
563
    public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
564
        boolean result = removeChildNode(node);
565
        Taxon taxon = node.getTaxon();
566
        node.setTaxon(null);
567
        taxon.removeTaxonNode(node);
568
        if (deleteChildren){
569
            ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
570
            for(TaxonNode childNode : childNodes){
571
                node.deleteChildNode(childNode, deleteChildren);
572
            }
573
        } else{
574
        	ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
575
            for(TaxonNode childNode : childNodes){
576
             this.addChildNode(childNode, null, null);
577
            }
578
        }
579
580
        return result;
581
    }
582
583
    /**
584
     * Removes the child node from this node. Sets the parent and the classification of the child
585
     * node to null
586
     *
587
     * @param childNode
588
     * @return
589
     */
590
    protected boolean removeChildNode(TaxonNode childNode){
591
        boolean result = true;
592 541f08b0 Katja Luther
        //removeNullValueFromChildren();
593 f37d249f Andreas Müller
        if(childNode == null){
594
            throw new IllegalArgumentException("TaxonNode may not be null");
595
        }
596
        int index = childNodes.indexOf(childNode);
597
        if (index >= 0){
598
            removeChild(index);
599
        } else {
600
            result = false;
601
        }
602
        return result;
603
    }
604
605
    /**
606
     * Removes the child node placed at the given (index + 1) position
607
     * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
608
     * Sets the parent and the classification of the child
609
     * node to null.
610
     * If the given index is out of bounds no child will be removed.
611
     *
612
     * @param  index	the integer indicating the position of the taxon node to
613
     * 					be removed
614
     * @see     		#getChildNodes()
615
     * @see				#addChildNode(TaxonNode, Reference, String)
616
     * @see				#addChildNode(TaxonNode, int, Reference, String)
617
     * @see				#deleteChildNode(TaxonNode)
618
     */
619
    public void removeChild(int index){
620 d5f57c61 k.luther
        //TODO: Only as a workaround. We have to find out why merge creates null entries.
621 f37d249f Andreas Müller
622
        TaxonNode child = childNodes.get(index);
623
        child = HibernateProxyHelper.deproxy(child, TaxonNode.class); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
624
625
        if (child != null){
626
627
            TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent(), TaxonNode.class);
628
            TaxonNode thisNode = HibernateProxyHelper.deproxy(this, TaxonNode.class);
629
            if(parent != null && parent != thisNode){
630
                throw new IllegalArgumentException("Child TaxonNode (id:" + child.getId() +") must be a child of this (id:" + thisNode.getId() + " node. Sortindex is: " + index + ", parent-id:" + parent.getId());
631
            }else if (parent == null){
632
                throw new IllegalStateException("Parent of child is null in TaxonNode.removeChild(int). This should not happen.");
633
            }
634
            childNodes.remove(index);
635
            child.setClassification(null);
636
637
            //update sortindex
638
            //TODO workaround (see sortIndex doc)
639
            this.countChildren = childNodes.size();
640
            child.setParent(null);
641
            child.setTreeIndex(null);
642
            updateSortIndex(index);
643
            child.setSortIndex(null);
644
        }
645
    }
646
647
648
    /**
649
     * Remove this taxonNode From its taxonomic parent
650
     *
651
     * @return true on success
652
     */
653
    public boolean delete(){
654
        if(isTopmostNode()){
655
            return classification.deleteChildNode(this);
656
        }else{
657
            return getParent().deleteChildNode(this);
658
        }
659
    }
660
661
    /**
662
     * Remove this taxonNode From its taxonomic parent
663
     *
664
     * @return true on success
665
     */
666
    public boolean delete(boolean deleteChildren){
667
        if(isTopmostNode()){
668
            return classification.deleteChildNode(this, deleteChildren);
669
        }else{
670
            return getParent().deleteChildNode(this, deleteChildren);
671
        }
672
    }
673
674
    @Override
675
    @Deprecated //for CDM lib internal use only, may be removed in future versions
676
    public int treeId() {
677
        if (this.classification == null){
678
        	logger.warn("TaxonNode has no classification. This should not happen.");  //#3840
679
        	return -1;
680
        }else{
681
        	return this.classification.getId();
682
        }
683
    }
684
685
686
    /**
687
     * Sets the parent of this taxon node to the given parent. Cleans up references to
688
     * old parents and sets the classification to the new parents classification
689
     *
690
     * @param parent
691
     */
692
    @Transient
693
    protected void setParentTreeNode(TaxonNode parent, int index){
694
        // remove ourselves from the old parent
695
        TaxonNode formerParent = this.getParent();
696
        formerParent = HibernateProxyHelper.deproxy(formerParent, TaxonNode.class);
697
        if (formerParent != null){
698
        	//special case, child already exists for same parent
699
            //FIXME document / check for correctness
700
            if (formerParent.equals(parent)){
701
                int currentIndex = formerParent.getChildNodes().indexOf(this);
702
                if (currentIndex != -1 && currentIndex < index){
703
                    index--;
704
                }
705
        	}
706
707
        	//remove from old parent
708
            formerParent.removeChildNode(this);
709
        }
710
711
        // set the new parent
712
        setParent(parent);
713
714
        // set the classification to the parents classification
715
716
        Classification classification = parent.getClassification();
717
        //FIXME also set the tree index here for performance reasons
718 5df69434 Katja Luther
        classification = HibernateProxyHelper.deproxy(classification, Classification.class);
719 f37d249f Andreas Müller
        setClassificationRecursively(classification);
720
        // add this node to the parent's child nodes
721 5df69434 Katja Luther
        parent = HibernateProxyHelper.deproxy(parent, TaxonNode.class);
722 f37d249f Andreas Müller
        List<TaxonNode> parentChildren = parent.getChildNodes();
723 205e4322 k.luther
       //TODO: Only as a workaround. We have to find out why merge creates null entries.
724 c5aa066b Katja Luther
725 08a698af Katja Luther
//        HHH_9751_Util.removeAllNull(parentChildren);
726
//        parent.updateSortIndex(0);
727 b37deaa3 Katja Luther
        if (index > parent.getChildNodes().size()){
728
            index = parent.getChildNodes().size();
729
        }
730 f37d249f Andreas Müller
        if (parentChildren.contains(this)){
731
            //avoid duplicates
732
            if (parentChildren.indexOf(this) < index){
733
                index = index -1;
734
            }
735
            parentChildren.remove(this);
736
            parentChildren.add(index, this);
737
        }else{
738
            parentChildren.add(index, this);
739
        }
740
741
742
        //sortIndex
743
        //TODO workaround (see sortIndex doc)
744 fcd142d1 Katja Luther
       // this.getParent().removeNullValueFromChildren();
745 f37d249f Andreas Müller
        this.getParent().updateSortIndex(index);
746
        //only for debugging
747
        if (! this.getSortIndex().equals(index)){
748 d818257c Andreas Müller
        	logger.warn("index and sortindex are not equal: "+  this.getSortIndex() +";" + index);
749 f37d249f Andreas Müller
        }
750
751
        // update the children count
752
        parent.setCountChildren(parent.getChildNodes().size());
753
    }
754
755
	/**
756
	 * As long as the sort index is not correctly handled through hibernate this is a workaround method
757
	 * to update the sort index manually
758
	 * @param parentChildren
759
	 * @param index
760
	 */
761
	private void updateSortIndex(int index) {
762 1e4e650e Katja Luther
	    if (this.hasChildNodes()){
763
    	    List<TaxonNode> children = this.getChildNodes();
764 fcd142d1 Katja Luther
    	    HHH_9751_Util.removeAllNull(children);
765 1e4e650e Katja Luther
    	    for(int i = index; i < children.size(); i++){
766
                	TaxonNode child = children.get(i);
767
                	if (child != null){
768
        //        		child = CdmBase.deproxy(child, TaxonNode.class);  //deproxy not needed as long as setSortIndex is protected or public #4200
769
                		child.setSortIndex(i);
770
                	}else{
771
                		String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";
772
                		throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));
773
                	}
774
            }
775
	    }
776 f37d249f Andreas Müller
	}
777
778
779
780
781
    /**
782
     * Returns a set containing this node and all nodes that are descendants of this node
783
     *
784
     * @return
785
     */
786
787
    protected Set<TaxonNode> getDescendants(){
788
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
789
790
        nodeSet.add(this);
791
792
        for(TaxonNode childNode : getChildNodes()){
793
            nodeSet.addAll(childNode.getDescendants());
794
        }
795
796
        return nodeSet;
797
    }
798
799
    /**
800
     * Returns a set containing a clone of this node and of all nodes that are descendants of this node
801
     *
802
     * @return
803
     */
804
    protected TaxonNode cloneDescendants(){
805
806
        TaxonNode clone = (TaxonNode)this.clone();
807
        TaxonNode childClone;
808
809
        for(TaxonNode childNode : getChildNodes()){
810
            childClone = (TaxonNode) childNode.clone();
811
            for (TaxonNode childChild:childNode.getChildNodes()){
812
                childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());
813
            }
814
            clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference());
815
            //childClone.addChildNode(childNode.cloneDescendants());
816
        }
817
        return clone;
818
    }
819
820
    /**
821 aa478e85 Patrick Plitzner
     * Returns all ancestor nodes of this node
822 f37d249f Andreas Müller
     *
823 aa478e85 Patrick Plitzner
     * @return a set of all parent nodes
824 f37d249f Andreas Müller
     */
825
    protected Set<TaxonNode> getAncestors(){
826
        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
827
        if(this.getParent() != null){
828
        	TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);
829 aa478e85 Patrick Plitzner
        	nodeSet.add(parent);
830 f37d249f Andreas Müller
            nodeSet.addAll(parent.getAncestors());
831
        }
832
        return nodeSet;
833
    }
834 9f1aa368 Andreas Müller
835 aa478e85 Patrick Plitzner
    /**
836
     * Retrieves the first ancestor of the given rank
837
     * @param rank the rank the ancestor should have
838
     * @return the first found instance of a parent taxon node with the given rank
839
     */
840 f37d249f Andreas Müller
    public TaxonNode getAncestorOfRank(Rank rank){
841 1e4e650e Katja Luther
        TaxonBase taxon = HibernateProxyHelper.deproxy(this.getTaxon(), Taxon.class);
842 a6035e28 Katja Luther
        if (taxon == null){
843
            return null;
844
        }
845
        TaxonNameBase name = HibernateProxyHelper.deproxy(taxon.getName(), TaxonNameBase.class);
846
        if (name.getRank().isHigher(rank)){
847
        	return null;
848 f37d249f Andreas Müller
        }
849 a6035e28 Katja Luther
        if (name.getRank().equals(rank)){
850
        	return this;
851
        }
852
853
        if(this.getParent() != null){
854 f37d249f Andreas Müller
        	TaxonNode parent =  CdmBase.deproxy(this.getParent(), TaxonNode.class);
855
            return parent.getAncestorOfRank(rank);
856
        }
857
		return null;
858
859
    }
860
861
    /**
862
     * Whether this TaxonNode is a direct child of the classification TreeNode
863
     * @return
864
     */
865
    @Transient
866
    public boolean isTopmostNode(){
867
    	boolean parentCheck = false;
868
    	boolean classificationCheck = false;
869
870
    	if(getParent() != null) {
871
    		if(getParent().getTaxon() == null) {
872
    			parentCheck = true;
873
    		}
874
    	}
875
876
    	//TODO remove
877
    	// FIXME This should work but doesn't, due to missing sort indexes, can be removed after fixing #4200, #4098
878
    	if (classification != null){
879
    		classificationCheck = classification.getRootNode().getChildNodes().contains(this);
880
    	}else{
881
    		classificationCheck = false;
882
    	}
883
884
    	// The following is just for logging purposes for the missing sort indexes problem
885
    	// ticket #4098
886
    	if(parentCheck != classificationCheck) {
887
    		logger.warn("isTopmost node check " + parentCheck + " not same as classificationCheck : " + classificationCheck + " for taxon node ");
888
    		if(this.getParent() != null) {
889
    			logger.warn("-- with parent uuid " + this.getParent().getUuid().toString());
890
    			logger.warn("-- with parent id " + this.getParent().getId());
891
    			for(TaxonNode node : this.getParent().getChildNodes()) {
892
    				if(node == null) {
893
    					logger.warn("-- child node is null");
894
    				} else if (node.getTaxon() == null) {
895
    					logger.warn("-- child node taxon is null");
896
    				}
897
    			}
898
    			logger.warn("-- parent child count" + this.getParent().getChildNodes().size());
899
    		}
900
    	}
901
902
    	return parentCheck;
903
    }
904
905
    /**
906
     * Whether this TaxonNode is a descendant of the given TaxonNode
907
     *
908
     * Caution: use this method with care on big branches. -> performance and memory hungry
909
     *
910
     * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
911
     * other direction (up). It will always result in a rather small set of consecutive parents beeing
912
     * generated.
913
     *
914
     * TODO implement more efficiently without generating the set of descendants first
915
     *
916
     * @param possibleParent
917
     * @return true if this is a descendant
918
     */
919
    @Transient
920
    public boolean isDescendant(TaxonNode possibleParent){
921
    	if (this.treeIndex() == null || possibleParent.treeIndex() == null) {
922
    		return false;
923
    	}
924
    	return possibleParent == null ? false : this.treeIndex().startsWith(possibleParent.treeIndex() );
925
    }
926
927
    /**
928
     * Whether this TaxonNode is an ascendant of the given TaxonNode
929
     *
930
     * @param possibleChild
931
     * @return true if there are ascendants
932
     */
933
    @Transient
934
    public boolean isAncestor(TaxonNode possibleChild){
935
    	if (this.treeIndex() == null || possibleChild.treeIndex() == null) {
936
    		return false;
937
    	}
938
       // return possibleChild == null ? false : possibleChild.getAncestors().contains(this);
939
        return possibleChild == null ? false : possibleChild.treeIndex().startsWith(this.treeIndex());
940
    }
941
942
    /**
943
     * Whether this taxon has child nodes
944
     *
945
     * @return true if the taxonNode has childNodes
946
     */
947
    @Transient
948
    @Override
949
    public boolean hasChildNodes(){
950
        return childNodes.size() > 0;
951
    }
952
953
954
    public boolean hasTaxon() {
955
        return (taxon!= null);
956
    }
957
958
    /**
959
     * @return
960
     */
961
    @Transient
962
    public Rank getNullSafeRank() {
963
        return hasTaxon() ? getTaxon().getNullSafeRank() : null;
964
    }
965
966 1e4e650e Katja Luther
    public void removeNullValueFromChildren(){
967 d9c82be9 Andreas Kohlbecker
        try {
968 fcd142d1 Katja Luther
            //HHH_9751_Util.removeAllNull(childNodes);
969 d9c82be9 Andreas Kohlbecker
            this.updateSortIndex(0);
970
        } catch (LazyInitializationException e) {
971
            logger.info("Cannot clean up uninitialized children without a session, skipping.");
972 c5aa066b Katja Luther
        }
973
    }
974
975 f37d249f Andreas Müller
976
977
//*********************** CLONE ********************************************************/
978
    /**
979
     * Clones <i>this</i> taxon node. This is a shortcut that enables to create
980
     * a new instance that differs only slightly from <i>this</i> taxon node by
981
     * modifying only some of the attributes.<BR><BR>
982
     * The child nodes are not copied.<BR>
983
     * The taxon and parent are the same as for the original taxon node. <BR>
984
     *
985
     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
986
     * @see java.lang.Object#clone()
987
     */
988
    @Override
989
    public Object clone()  {
990
        try{
991
            TaxonNode result = (TaxonNode)super.clone();
992
            result.getTaxon().addTaxonNode(result);
993 8e6c53f9 Andreas Müller
994
            //childNodes
995
            result.childNodes = new ArrayList<>();
996 f37d249f Andreas Müller
            result.countChildren = 0;
997
998
            //agents
999 8e6c53f9 Andreas Müller
            result.agentRelations = new HashSet<>();
1000 f37d249f Andreas Müller
            for (TaxonNodeAgentRelation rel : this.agentRelations){
1001
                result.addAgentRelation((TaxonNodeAgentRelation)rel.clone());
1002
            }
1003
1004 8e6c53f9 Andreas Müller
            //excludedNote
1005
            result.excludedNote = new HashMap<>();
1006
            for(Language lang : this.excludedNote.keySet()){
1007
                result.excludedNote.put(lang, this.excludedNote.get(lang));
1008
            }
1009
1010
1011 f37d249f Andreas Müller
            return result;
1012
        }catch (CloneNotSupportedException e) {
1013
            logger.warn("Object does not implement cloneable");
1014
            e.printStackTrace();
1015
            return null;
1016
        }
1017
    }
1018
1019
}