Project

General

Profile

Download (36 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

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

    
12
import java.util.ArrayList;
13
import java.util.HashMap;
14
import java.util.HashSet;
15
import java.util.List;
16
import java.util.Map;
17
import java.util.Set;
18

    
19
import javax.persistence.Column;
20
import javax.persistence.Entity;
21
import javax.persistence.FetchType;
22
import javax.persistence.JoinTable;
23
import javax.persistence.ManyToOne;
24
import javax.persistence.OneToMany;
25
import javax.persistence.OrderColumn;
26
import javax.persistence.Table;
27
import javax.persistence.Transient;
28
import javax.xml.bind.annotation.XmlAccessType;
29
import javax.xml.bind.annotation.XmlAccessorType;
30
import javax.xml.bind.annotation.XmlAttribute;
31
import javax.xml.bind.annotation.XmlElement;
32
import javax.xml.bind.annotation.XmlElementWrapper;
33
import javax.xml.bind.annotation.XmlIDREF;
34
import javax.xml.bind.annotation.XmlRootElement;
35
import javax.xml.bind.annotation.XmlSchemaType;
36
import javax.xml.bind.annotation.XmlType;
37
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
38

    
39
import org.apache.logging.log4j.LogManager;
40
import org.apache.logging.log4j.Logger;
41
import org.hibernate.annotations.Cascade;
42
import org.hibernate.annotations.CascadeType;
43
import org.hibernate.annotations.Parameter;
44
import org.hibernate.annotations.Type;
45
import org.hibernate.envers.Audited;
46
import org.hibernate.search.annotations.Analyze;
47
import org.hibernate.search.annotations.ContainedIn;
48
import org.hibernate.search.annotations.Field;
49
import org.hibernate.search.annotations.Index;
50
import org.hibernate.search.annotations.IndexedEmbedded;
51
import org.hibernate.search.annotations.Store;
52

    
53
import eu.etaxonomy.cdm.common.CdmUtils;
54
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
55
import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
56
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
57
import eu.etaxonomy.cdm.model.common.CdmBase;
58
import eu.etaxonomy.cdm.model.common.ITreeNode;
59
import eu.etaxonomy.cdm.model.common.Language;
60
import eu.etaxonomy.cdm.model.common.LanguageString;
61
import eu.etaxonomy.cdm.model.common.MultilanguageText;
62
import eu.etaxonomy.cdm.model.common.SingleSourcedEntityBase;
63
import eu.etaxonomy.cdm.model.name.Rank;
64
import eu.etaxonomy.cdm.model.name.TaxonName;
65
import eu.etaxonomy.cdm.model.reference.NamedSource;
66
import eu.etaxonomy.cdm.model.reference.Reference;
67
import eu.etaxonomy.cdm.model.term.DefinedTerm;
68
import eu.etaxonomy.cdm.validation.Level3;
69
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustBeLowerRankThanParent;
70
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustDeriveNameFromParent;
71
import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustNotSkipRanks;
72

    
73
/**
74
 * @author a.mueller
75
 * @since 31.03.2009
76
 */
77
@XmlAccessorType(XmlAccessType.FIELD)
78
@XmlType(name = "TaxonNode", propOrder = {
79
    "classification",
80
    "taxon",
81
    "parent",
82
    "treeIndex",
83
    "childNodes",
84
    "countChildren",
85
    "agentRelations",
86
    "synonymToBeUsed",
87
    "status",
88
    "statusNote"
89
})
90
@XmlRootElement(name = "TaxonNode")
91
@Entity
92
//@Indexed disabled to reduce clutter in indexes, since this type is not used by any search
93
//@Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonNode")
94
@Audited
95
@Table(name="TaxonNode", indexes = { @javax.persistence.Index(name = "taxonNodeTreeIndex", columnList = "treeIndex") })
96
@ChildTaxaMustBeLowerRankThanParent(groups = Level3.class)
97
@ChildTaxaMustNotSkipRanks(groups = Level3.class)
98
@ChildTaxaMustDeriveNameFromParent(groups = Level3.class)
99
public class TaxonNode
100
            extends SingleSourcedEntityBase
101
            implements ITaxonTreeNode, ITreeNode<TaxonNode>{
102

    
103
    private static final long serialVersionUID = -4743289894926587693L;
104
    private static final Logger logger = LogManager.getLogger(TaxonNode.class);
105

    
106
    @XmlElement(name = "taxon")
107
    @XmlIDREF
108
    @XmlSchemaType(name = "IDREF")
109
    @ManyToOne(fetch = FetchType.LAZY)
110
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
111
    @ContainedIn
112
    private Taxon taxon;
113

    
114
    @XmlElement(name = "parent")
115
    @XmlIDREF
116
    @XmlSchemaType(name = "IDREF")
117
    @ManyToOne(fetch = FetchType.LAZY)
118
//    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
119
    private TaxonNode parent;
120

    
121
    @XmlElement(name = "treeIndex")
122
    @Column(length=255)
123
    @Field(store = Store.YES, index = Index.YES, analyze = Analyze.NO)
124
    private String treeIndex;
125

    
126
    @XmlElement(name = "classification")
127
    @XmlIDREF
128
    @XmlSchemaType(name = "IDREF")
129
    @ManyToOne(fetch = FetchType.LAZY)
130
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
131
//	TODO @NotNull // avoids creating a UNIQUE key for this field
132
    @IndexedEmbedded(includeEmbeddedObjectId=true)
133
    private Classification classification;
134

    
135
    @XmlElementWrapper(name = "childNodes")
136
    @XmlElement(name = "childNode")
137
    @XmlIDREF
138
    @XmlSchemaType(name = "IDREF")
139
    //see https://dev.e-taxonomy.eu/redmine/issues/3722
140
    //see https://dev.e-taxonomy.eu/redmine/issues/4200
141
    //see https://dev.e-taxonomy.eu/redmine/issues/8127
142
    //see https://dev.e-taxonomy.eu/redmine/issues/10067
143
    @OrderColumn(name="sortIndex", nullable=true)
144
    @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
145
    //do not cascade
146
    private List<TaxonNode> childNodes = new ArrayList<>();
147

    
148
    @XmlElement(name = "countChildren")
149
    private int countChildren;
150

    
151
    @XmlElementWrapper(name = "agentRelations")
152
    @XmlElement(name = "agentRelation")
153
    @XmlIDREF
154
    @XmlSchemaType(name = "IDREF")
155
    @OneToMany(mappedBy="taxonNode", fetch=FetchType.LAZY)
156
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
157
    private Set<TaxonNodeAgentRelation> agentRelations = new HashSet<>();
158

    
159
    /**
160
     * The {@link TaxonNodeStatus status} of this taxon node.
161
     */
162
    @XmlAttribute(name ="TaxonNodeStatus")
163
    @Column(name="status", length=10)
164
    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
165
        parameters = {@Parameter(name  = "enumClass", value = "eu.etaxonomy.cdm.model.taxon.TaxonNodeStatus")}
166
    )
167
    @Audited
168
    private TaxonNodeStatus status;
169

    
170
    @XmlElement(name = "statusNote")
171
    @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
172
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
173
//    @MapKeyJoinColumn(name="statusNote_mapkey_id")
174
    @JoinTable(name = "TaxonNode_StatusNote")  //to make possible to add also unplacedNote
175
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
176
    private Map<Language,LanguageString> statusNote = new HashMap<>();
177

    
178
//	private Taxon originalConcept;
179
//	//or
180
    @XmlElement(name = "synonymToBeUsed")
181
    @XmlIDREF
182
    @XmlSchemaType(name = "IDREF")
183
    @ManyToOne(fetch = FetchType.LAZY)
184
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
185
    private Synonym synonymToBeUsed;
186

    
187
// ******************** CONSTRUCTOR **********************************************/
188

    
189
    //for hibernate use only, *packet* private required by bytebuddy
190
    @Deprecated
191
    TaxonNode(){}
192

    
193
    /**
194
     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
195
     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
196
     * @param taxon
197
     * @param classification
198
     * @deprecated setting of classification is handled in the addTaxonNode() method,
199
     * use TaxonNode(taxon) instead
200
     */
201
    @Deprecated
202
    protected TaxonNode (Taxon taxon, Classification classification){
203
        this(taxon);
204
        setClassification(classification);
205
    }
206

    
207
    /**
208
     * To create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
209
     * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
210
     *
211
     * @param taxon
212
     */
213
    protected TaxonNode(Taxon taxon){
214
        setTaxon(taxon);
215
    }
216

    
217
// ************************* GETTER / SETTER *******************************/
218

    
219
    public Taxon getTaxon() {
220
        return taxon;
221
    }
222
    public void setTaxon(Taxon taxon) {
223
        this.taxon = taxon;
224
        if (taxon != null){
225
            taxon.addTaxonNode(this);
226
        }
227
    }
228

    
229
    @Override
230
    public List<TaxonNode> getChildNodes() {
231
        return childNodes;
232
    }
233
	protected void setChildNodes(List<TaxonNode> childNodes) {
234
		this.childNodes = childNodes;
235
	}
236

    
237
    public Classification getClassification() {
238
        return classification;
239
    }
240
    /**
241
     * THIS METHOD SHOULD NOT BE CALLED!
242
     * invisible part of the bidirectional relationship, for public use Classification.addRoot() or TaxonNode.addChild()
243
     * @param classification
244
     * @deprecated for internal use only
245
     */
246
    @Deprecated
247
    protected void setClassification(Classification classification) {
248
        this.classification = classification;
249
    }
250

    
251
    public boolean isUnplaced() {return hasStatus(TaxonNodeStatus.UNPLACED);}
252

    
253
    //#8281 indicates a preliminary placement
254
    public boolean isDoubtful() {return hasStatus(TaxonNodeStatus.DOUBTFUL);}
255

    
256
    public boolean isExcluded() {return hasStatus(TaxonNodeStatus.EXCLUDED);}
257

    
258
//************************* SOURCE *********************/
259

    
260
//    @Override
261
//    @Transient
262
//    public String getMicroReference() {
263
//        return source == null ? null : this.source.getCitationMicroReference();
264
//    }
265
//    public void setMicroReference(String microReference) {
266
//        this.getSource(true).setCitationMicroReference(StringUtils.isBlank(microReference)? null : microReference);
267
//        checkNullSource();
268
//    }
269
//
270
//    @Override
271
//    @Transient
272
//    public Reference getReference() {
273
//        return (this.source == null) ? null : source.getCitation();
274
//    }
275
//    public void setReference(Reference reference) {
276
//        getSource(true).setCitation(reference);
277
//        checkNullSource();
278
//    }
279

    
280
//    @Override
281
//    public DescriptionElementSource getSource() {
282
//        return source;
283
//    }
284
//    @Override
285
//    public void setSource(DescriptionElementSource source) {
286
//        this.source = source;
287
//    }
288

    
289

    
290

    
291
//************************************************************/
292

    
293
    /**
294
     * The computed order index of this node being a child in the parents
295
     * childnode list.
296
     */
297
    @Transient
298
    public Integer getSortIndex() {
299
        return getParent() == null ? null : getParent().getChildNodes().indexOf(CdmBase.deproxy(this));
300
    }
301

    
302
    //countChildren
303
    public int getCountChildren() {
304
        return countChildren;
305
    }
306
    /**
307
     * @deprecated for internal use only
308
     * @param countChildren
309
     */
310
    @Deprecated
311
    protected void setCountChildren(int countChildren) {
312
        this.countChildren = countChildren;
313
    }
314

    
315
    //parent
316
    @Override
317
    public TaxonNode getParent(){
318
        return parent;
319
    }
320
    /**
321
     * Sets the parent of this taxon node.<BR>
322
     *
323
     * In most cases you would want to call setParentTreeNode(ITreeNode) which
324
     * handles updating of the bidirectional relationship
325
     *
326
     * @see setParentTreeNode(ITreeNode)
327
     * @param parent
328
     *
329
     */
330
    protected void setParent(TaxonNode parent) {
331
        this.parent = parent;
332
//        this.treeIndex = parent.treeIndex() +
333
    }
334

    
335
    public TaxonNodeStatus getStatus() {
336
        return status;
337
    }
338
    public void setStatus(TaxonNodeStatus status) {
339
        this.status = status;
340
    }
341

    
342
// *************** Status Note ***************
343

    
344
    /**
345
     * Returns the {@link MultilanguageText multi-language text} to add a note to the
346
     * status. The different {@link LanguageString language strings}
347
     * contained in the multi-language text should all have the same meaning.
348
     * @see #getStatusNote(Language)
349
     * @see #putStatusNote(Language, String)
350
     */
351
    public Map<Language,LanguageString> getStatusNote(){
352
        return this.statusNote;
353
    }
354

    
355
    /**
356
     * Returns the status note string in the given {@link Language language}
357
     *
358
     * @param language  the language in which the description string looked for is formulated
359
     * @see             #getStatusNote()
360
     * @see             #putStatusNote(Language, String)
361
     */
362
    public String getStatusNote(Language language){
363
        LanguageString languageString = statusNote.get(language);
364
        if (languageString == null){
365
            return null;
366
        }else{
367
            return languageString.getText();
368
        }
369
    }
370

    
371
    /**
372
     * Adds a translated {@link LanguageString text in a particular language}
373
     * to the {@link MultilanguageText multilanguage text} used to add a note to
374
     * the {@link #getStatus() status}.
375
     *
376
     * @param statusNote   the language string adding a note to the status
377
     *                      in a particular language
378
     * @see                 #getStatusNote()
379
     * @see                 #putStatusNote(String, Language)
380
     */
381
    public void putStatusNote(LanguageString statusNote){
382
        this.statusNote.put(statusNote.getLanguage(), statusNote);
383
    }
384
    /**
385
     * Creates a {@link LanguageString language string} based on the given text string
386
     * and the given {@link Language language} and adds it to the {@link MultilanguageText
387
     * multi-language text} used to annotate the status.
388
     *
389
     * @param text      the string annotating the status
390
     *                  in a particular language
391
     * @param language  the language in which the text string is formulated
392
     * @see             #getStatusNote()
393
     * @see             #putStatusNote(LanguageString)
394
     * @see             #removeStatusNote(Language)
395
     */
396
    public void putStatusNote(Language language, String text){
397
        this.statusNote.put(language, LanguageString.NewInstance(text, language));
398
    }
399

    
400
    /**
401
     * Removes from the {@link MultilanguageText multilanguage text} used to annotate
402
     * the status the one {@link LanguageString language string}
403
     * with the given {@link Language language}.
404
     *
405
     * @param  lang the language in which the language string to be removed
406
     *       has been formulated
407
     * @see         #getStatusNote()
408
     */
409
    public void removeStatusNote(Language lang){
410
        this.statusNote.remove(lang);
411
    }
412

    
413
// ****************** Agent Relations ****************************/
414

    
415
    public Set<TaxonNodeAgentRelation> getAgentRelations() {
416
        return this.agentRelations;
417
    }
418
    public TaxonNodeAgentRelation addAgentRelation(DefinedTerm type, TeamOrPersonBase<?> agent){
419
        TaxonNodeAgentRelation result = TaxonNodeAgentRelation.NewInstance(this, agent, type);
420
        return result;
421
    }
422
    public void addAgentRelation(TaxonNodeAgentRelation agentRelation) {
423
        agentRelation.setTaxonNode(this);
424
        this.agentRelations.add(agentRelation);
425
    }
426
    public void removeAgentRelation(TaxonNodeAgentRelation agentRelation) {
427
        agentRelation.setTaxonNode(this);
428
        agentRelations.remove(agentRelation);
429
    }
430

    
431
//********************
432

    
433
    //synonymToBeused
434
    public Synonym getSynonymToBeUsed() {
435
        return synonymToBeUsed;
436
    }
437
    public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
438
        this.synonymToBeUsed = synonymToBeUsed;
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
    @Override
452
    public String treeIndexLike() {
453
        return treeIndex + "%";
454
    }
455
    @Override
456
    public String treeIndexWc() {
457
        return treeIndex + "*";
458
    }
459

    
460
//************************ METHODS **************************/
461

    
462
   @Override
463
    public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
464
        return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
465
    }
466

    
467
   @Override
468
   public TaxonNode addChildTaxon(Taxon taxon, NamedSource source) {
469
       return addChildTaxon(taxon, this.childNodes.size(), source);
470
   }
471

    
472
    @Override
473
    public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
474
        return addChildTaxon(taxon, index, NamedSource.NewPrimarySourceInstance(citation, microCitation));
475
    }
476

    
477
    @Override
478
    public TaxonNode addChildTaxon(Taxon taxon, int index, NamedSource source) {
479
        Classification classification = CdmBase.deproxy(this.getClassification());
480
        taxon = HibernateProxyHelper.deproxy(taxon, Taxon.class);
481
        if (classification.isTaxonInTree(taxon)){
482
            throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));
483
       }
484
       return addChildNode(new TaxonNode(taxon), index, source);
485
    }
486

    
487
    /**
488
     * Moves a taxon node to a new parent. Descendents of the node are moved as well
489
     *
490
     * @param childNode the taxon node to be moved to the new parent
491
     * @return the child node in the state of having a new parent
492
     */
493
    @Override
494
    public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference){
495
        addChildNode(childNode, childNodes.size(), reference, microReference);
496
        return childNode;
497
    }
498

    
499
    /**
500
     * Inserts the given taxon node in the list of children of <i>this</i> taxon node
501
     * at the given (index + 1) position. If the given index is out of bounds
502
     * an exception will arise.<BR>
503
     * Due to bidirectionality this method must also assign <i>this</i> taxon node
504
     * as the parent of the given child.
505
     *
506
     * @param	child	the taxon node to be added
507
     * @param	index	the integer indicating the position at which the child
508
     * 					should be added
509
     * @see				#getChildNodes()
510
     * @see				#addChildNode(TaxonNode, Reference, String, Synonym)
511
     * @see				#deleteChildNode(TaxonNode)
512
     * @see				#deleteChildNode(int)
513
     */
514
    @Override
515
    public TaxonNode addChildNode(TaxonNode child, int index, Reference reference, String microReference){
516
        return addChildNode(child, index, NamedSource.NewPrimarySourceInstance(reference, microReference));
517
    }
518

    
519

    
520
    /**
521
     * Inserts the given taxon node in the list of children of <i>this</i> taxon node
522
     * at the given (index + 1) position. If the given index is out of bounds
523
     * an exception will arise.<BR>
524
     * Due to bidirectionality this method must also assign <i>this</i> taxon node
525
     * as the parent of the given child.
526
     *
527
     * @param   child   the taxon node to be added
528
     * @param   index   the integer indicating the position at which the child
529
     *                  should be added
530
     * @see             #getChildNodes()
531
     * @see             #addChildNode(TaxonNode, Reference, String, Synonym)
532
     * @see             #deleteChildNode(TaxonNode)
533
     * @see             #deleteChildNode(int)
534
     */
535
    @Override
536
    public TaxonNode addChildNode(TaxonNode child, int index, NamedSource source){
537
        if (index < 0 || index > childNodes.size() + 1){
538
            throw new IndexOutOfBoundsException("Wrong index: " + index);
539
        }
540
           // check if this node is a descendant of the childNode
541
        if(child.getParent() != this && child.isAncestor(this)){
542
            throw new IllegalStateException("New parent node is a descendant of the node to be moved.");
543
        }
544

    
545
        child.setParentTreeNode(this, index);
546

    
547
        child.setSource(source);
548

    
549
        return child;
550
    }
551

    
552
    /**
553
     * Sets this nodes classification. Updates classification of child nodes recursively.
554
     *
555
     * If the former and the actual tree are equal() this method does nothing.
556
     *
557
     * @throws IllegalArgumentException if newClassifciation is null
558
     *
559
     * @param newClassification
560
     */
561
    @Transient
562
    private void setClassificationRecursively(Classification newClassification) {
563
        if (newClassification == null){
564
        	throw new IllegalArgumentException("New Classification must not be 'null' when setting new classification.");
565
        }
566
    	if(! newClassification.equals(this.getClassification())){
567
            this.setClassification(newClassification);
568
            for(TaxonNode childNode : this.getChildNodes()){
569
                childNode.setClassificationRecursively(newClassification);
570
            }
571
        }
572
    }
573

    
574

    
575

    
576

    
577
    @Override
578
    public boolean deleteChildNode(TaxonNode node) {
579
        boolean result = removeChildNode(node);
580
        Taxon taxon = deproxy(node.getTaxon());
581
        node = deproxy(node);
582
        node.setTaxon(null);
583

    
584
        ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
585
        for(TaxonNode childNode : childNodes){
586
            HibernateProxyHelper.deproxy(childNode, TaxonNode.class);
587
            node.deleteChildNode(childNode);
588
        }
589
        taxon.removeTaxonNode(node);
590
        return result;
591
    }
592

    
593
    /**
594
     * Deletes the child node and also removes children of childnode
595
     * recursively if delete children is <code>true</code>
596
     * @param node
597
     * @param deleteChildren
598
     * @return
599
     */
600
    public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
601
        boolean result = removeChildNode(node);
602
        Taxon taxon = node.getTaxon();
603
        node.setTaxon(null);
604
        taxon.removeTaxonNode(node);
605
        if (deleteChildren){
606
            ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
607
            for(TaxonNode childNode : childNodes){
608
                node.deleteChildNode(childNode, deleteChildren);
609
            }
610
        } else{
611
        	ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
612
            for(TaxonNode childNode : childNodes){
613
             this.addChildNode(childNode, null, null);
614
            }
615
        }
616

    
617
        return result;
618
    }
619

    
620
    /**
621
     * Removes the child node from this node. Sets the parent and the classification of the child
622
     * node to null
623
     *
624
     * @param childNode
625
     * @return
626
     */
627
    protected boolean removeChildNode(TaxonNode childNode){
628
        boolean result = true;
629
        //removeNullValueFromChildren();
630
        if(childNode == null){
631
            throw new IllegalArgumentException("TaxonNode may not be null");
632
        }
633
        int index = childNodes.indexOf(childNode);
634
        if (index >= 0){
635
            removeChild(index);
636
        } else {
637
            result = false;
638
        }
639
        return result;
640
    }
641

    
642
    /**
643
     * Removes the child node placed at the given (index + 1) position
644
     * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
645
     * Sets the parent and the classification of the child
646
     * node to null.
647
     * If the given index is out of bounds no child will be removed.<BR>
648
     * NOTE: this is more for inner use. It does not remove the node from the taxon!!
649
     * Use deleteChildNode(TaxonNode) instead
650
     *
651
     * @param  index	the integer indicating the position of the taxon node to
652
     * 					be removed
653
     * @see     		#getChildNodes()
654
     * @see				#addChildNode(TaxonNode, Reference, String)
655
     * @see				#addChildNode(TaxonNode, int, Reference, String)
656
     * @see				#deleteChildNode(TaxonNode)
657
     */
658
    public void removeChild(int index){
659
        //TODO: Only as a workaround. We have to find out why merge creates null entries.
660

    
661
        TaxonNode child = childNodes.get(index);
662
        child = HibernateProxyHelper.deproxy(child); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
663

    
664
        if (child != null){
665

    
666
            TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent());
667
            TaxonNode thisNode = HibernateProxyHelper.deproxy(this);
668
            if(parent != null && parent != thisNode){
669
                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());
670
            }else if (parent == null){
671
                throw new IllegalStateException("Parent of child is null in TaxonNode.removeChild(int). This should not happen.");
672
            }
673
            childNodes.remove(index);
674
            child.setClassification(null);
675

    
676
            this.countChildren = childNodes.size();
677
            child.setParent(null);
678
            child.setTreeIndex(null);
679
        }
680
    }
681

    
682
    /**
683
     * Remove this taxonNode From its taxonomic parent
684
     *
685
     * @return true on success
686
     */
687
    public boolean delete(){
688
        if(isTopmostNode()){
689
            return classification.deleteChildNode(this);
690
        }else{
691
            return getParent().deleteChildNode(this);
692
        }
693
    }
694

    
695
    /**
696
     * Remove this taxonNode From its taxonomic parent
697
     *
698
     * @return true on success
699
     */
700
    public boolean delete(boolean deleteChildren){
701
        if(isTopmostNode()){
702
            return classification.deleteChildNode(this, deleteChildren);
703
        }else{
704
            return getParent().deleteChildNode(this, deleteChildren);
705
        }
706
    }
707

    
708
    @Override
709
    @Deprecated //for CDM lib internal use only, may be removed in future versions
710
    public int treeId() {
711
        if (this.classification == null){
712
        	logger.warn("TaxonNode has no classification. This should not happen.");  //#3840
713
        	return -1;
714
        }else{
715
        	return this.classification.getId();
716
        }
717
    }
718

    
719

    
720
    /**
721
     * Sets the parent of this taxon node to the given parent. Cleans up references to
722
     * old parents and sets the classification to the new parents classification
723
     */
724
    @Transient
725
    protected void setParentTreeNode(TaxonNode parent, int index){
726
        // remove ourselves from the old parent
727
        TaxonNode formerParent = this.getParent();
728
        formerParent = CdmBase.deproxy(formerParent);
729
        if (formerParent != null){
730
        	//special case, child already exists for same parent
731
            //FIXME document / check for correctness
732
            if (formerParent.equals(parent)){
733
                int currentIndex = formerParent.getChildNodes().indexOf(this);
734
                if (currentIndex != -1 && currentIndex < index){
735
                    index--;
736
                }
737
        	}
738

    
739
        	//remove from old parent
740
            formerParent.removeChildNode(this);
741
        }
742

    
743
        // set the new parent
744
        setParent(parent);
745

    
746
        // set the classification to the parents classification
747

    
748
        Classification classification = parent.getClassification();
749
        //FIXME also set the tree index here for performance reasons
750
        classification = CdmBase.deproxy(classification);
751
        setClassificationRecursively(classification);
752
        // add this node to the parent's child nodes
753
        parent = CdmBase.deproxy(parent);
754
        List<TaxonNode> parentChildren = parent.getChildNodes();
755

    
756
        if (index > parent.getChildNodes().size()){
757
            index = parent.getChildNodes().size();
758
        }
759
        if (parentChildren.contains(this)){
760
            //avoid duplicates
761
            if (parentChildren.indexOf(this) < index){
762
                index = index-1;
763
            }
764
            parentChildren.remove(this);
765
            parentChildren.add(index, this);
766
        }else{
767
            parentChildren.add(index, this);
768
        }
769

    
770
//        //only for debugging
771
//        if (this.getSortIndex() == null){
772
//            logger.warn("sortindex is null. This should not happen.");
773
//        }else if (! this.getSortIndex().equals(index)){
774
//        	logger.warn("index and sortindex are not equal: " +  this.getSortIndex() + ";" + index);
775
//        }
776

    
777
        // update the children count
778
        parent.setCountChildren(parent.getChildNodes().size());
779
    }
780

    
781
    /**
782
     * Returns a set containing this node and all nodes that are descendants of this node.
783
     */
784
	@Transient
785
    protected Set<TaxonNode> getDescendants(){
786
        Set<TaxonNode> nodeSet = new HashSet<>();
787

    
788
        nodeSet.add(this);
789

    
790
        for(TaxonNode childNode : getChildNodes()){
791
            nodeSet.addAll(childNode.getDescendants());
792
        }
793

    
794
        return nodeSet;
795
    }
796

    
797
    /**
798
     * Returns a set containing a clone of this node and of all nodes that are descendants of this node
799
     *
800
     * @return
801
     */
802
    protected TaxonNode cloneDescendants(){
803

    
804
        TaxonNode clone = this.clone();
805
        TaxonNode childClone;
806

    
807
        for(TaxonNode childNode : getChildNodes()){
808
            childClone = childNode.clone();
809
            for (TaxonNode childChild:childNode.getChildNodes()){
810
                childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());
811
            }
812
            clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference());
813
            //childClone.addChildNode(childNode.cloneDescendants());
814
        }
815
        return clone;
816
    }
817

    
818
    /**
819
     * Returns all ancestor nodes of this node
820
     *
821
     * @return a set of all parent nodes
822
     */
823
    @Transient
824
    protected Set<TaxonNode> getAncestors(){
825
        Set<TaxonNode> nodeSet = new HashSet<>();
826
        if(this.getParent() != null){
827
        	TaxonNode parent =  CdmBase.deproxy(this.getParent());
828
        	nodeSet.add(parent);
829
            nodeSet.addAll(parent.getAncestors());
830
        }
831
        return nodeSet;
832
    }
833

    
834
    /**
835
     * Retrieves the first ancestor of the given rank. If any of the ancestors
836
     * has no taxon or has a rank > the given rank <code>null</code> is returned.
837
     * If <code>this</code> taxon is already of given rank this taxon is returned.
838
     * @param rank the rank the ancestor should have
839
     * @return the first found instance of a parent taxon node with the given rank
840
     */
841
    @Transient
842
    public TaxonNode getAncestorOfRank(Rank rank){
843
        Taxon taxon = CdmBase.deproxy(this.getTaxon());
844
        if (taxon == null){
845
            return null;
846
        }
847
        TaxonName name = CdmBase.deproxy(taxon.getName());
848
        if (name != null && name.getRank() != null){
849
            if (name.getRank().isHigher(rank)){
850
                return null;
851
            }
852
            if (name.getRank().equals(rank)){
853
                return this;
854
            }
855
        }
856

    
857
        if(this.getParent() != null){
858
        	TaxonNode parent =  CdmBase.deproxy(this.getParent());
859
            return parent.getAncestorOfRank(rank);
860
        }
861
		return null;
862
    }
863

    
864
    /**
865
     * Returns the ancestor taxa, starting with the highest (e.g. kingdom)
866
     * @return
867
     */
868
    @Transient
869
    public List<Taxon> getAncestorTaxaList(){
870
        List<Taxon> result = new ArrayList<>();
871
        TaxonNode current = this;
872
        while (current != null){
873
            if (current.getTaxon() != null){
874
                result.add(0, current.getTaxon());
875
            }
876
            current = current.getParent();
877
        }
878
        return result;
879
    }
880

    
881
    /**
882
     * Returns the ancestor taxon nodes, that do have a taxon attached
883
     * (excludes the root node) starting with the highest
884
     */
885
    @Transient
886
    public List<TaxonNode> getAncestorList(){
887
        List<TaxonNode> result = new ArrayList<>();
888
        TaxonNode current = this.getParent();
889
        while (current != null){
890
            if (current.getTaxon() != null){
891
                result.add(0, current);
892
            }
893
            current = current.getParent();
894
        }
895
        return result;
896
    }
897

    
898

    
899
    /**
900
     * Whether this TaxonNode is a direct child of the classification TreeNode
901
     */
902
    @Transient
903
    public boolean isTopmostNode(){
904
    	boolean parentCheck = false;
905
    	boolean classificationCheck = false;
906

    
907
    	if(getParent() != null) {
908
    		if(getParent().getTaxon() == null) {
909
    			parentCheck = true;
910
    		}
911
    	}
912

    
913
    	//TODO remove
914
    	// FIXME This should work but doesn't, due to missing sort indexes, can be removed after fixing #4200, #4098
915
    	if (classification != null){
916
    		classificationCheck = classification.getRootNode().getChildNodes().contains(this);
917
    	}else{
918
    		classificationCheck = false;
919
    	}
920

    
921
    	// The following is just for logging purposes for the missing sort indexes problem
922
    	// ticket #4098
923
    	if(parentCheck != classificationCheck) {
924
    		logger.warn("isTopmost node check " + parentCheck + " not same as classificationCheck : " + classificationCheck + " for taxon node ");
925
    		if(this.getParent() != null) {
926
    			logger.warn("-- with parent uuid " + this.getParent().getUuid().toString());
927
    			logger.warn("-- with parent id " + this.getParent().getId());
928
    			for(TaxonNode node : this.getParent().getChildNodes()) {
929
    				if(node == null) {
930
    					logger.warn("-- child node is null");
931
    				} else if (node.getTaxon() == null) {
932
    					logger.warn("-- child node taxon is null");
933
    				}
934
    			}
935
    			logger.warn("-- parent child count" + this.getParent().getChildNodes().size());
936
    		}
937
    	}
938

    
939
    	return parentCheck;
940
    }
941

    
942
    /**
943
     * Whether this TaxonNode is a descendant of (or equal to) the given TaxonNode
944
     *
945
     * @param possibleParent
946
     * @return <code>true</code> if <b>this</b> is a descendant
947
     */
948
    @Transient
949
    public boolean isDescendant(TaxonNode possibleParent){
950
    	if (possibleParent == null || this.treeIndex() == null
951
    	        || possibleParent.treeIndex() == null) {
952
    		return false;
953
    	}
954
    	return this.treeIndex().startsWith(possibleParent.treeIndex() );
955
    }
956

    
957
    /**
958
     * Whether this TaxonNode is an ascendant of (or equal to) the given TaxonNode.
959
     *
960
     * @param possibleChild
961
     * @return <code>true</code> if <b>this</b> is a ancestor of the given child parameter
962
     */
963
    @Transient
964
    public boolean isAncestor(TaxonNode possibleChild){
965
    	if (possibleChild == null || this.treeIndex() == null || possibleChild.treeIndex() == null) {
966
    		return false;
967
    	}
968
       // return possibleChild == null ? false : possibleChild.getAncestors().contains(this);
969
        return  possibleChild.treeIndex().startsWith(this.treeIndex());
970
    }
971

    
972
    /**
973
     * Whether this taxon has child nodes
974
     *
975
     * @return true if the taxonNode has childNodes
976
     */
977
    @Transient
978
    @Override
979
    public boolean hasChildNodes(){
980
        return childNodes.size() > 0;
981
    }
982

    
983
    public boolean hasTaxon() {
984
        return (taxon!= null);
985
    }
986

    
987
    @Transient
988
    public Rank getNullSafeRank() {
989
        return hasTaxon() ? getTaxon().getNullSafeRank() : null;
990
    }
991

    
992
    @Transient
993
    public TaxonName getNullSafeName() {
994
        return getTaxon() == null? null: getTaxon().getName();
995
    }
996

    
997
    private boolean hasStatus(TaxonNodeStatus status) {
998
        return CdmUtils.nullSafeEqual(this.status, status);
999
    }
1000

    
1001
//*********************** CLONE ********************************************************/
1002
    /**
1003
     * Clones <i>this</i> taxon node. This is a shortcut that enables to create
1004
     * a new instance that differs only slightly from <i>this</i> taxon node by
1005
     * modifying only some of the attributes.<BR><BR>
1006
     * The child nodes are not copied.<BR>
1007
     * The taxon and parent are the same as for the original taxon node. <BR>
1008
     * <BR>
1009
     * Note: Cloning taxon nodes with cloning taxa (and children) is a complex
1010
     * issue which is better be handled in service layer logic. See according
1011
     * clone method in classification service
1012
     * or taxon node service there.
1013
     *
1014
     * @see java.lang.Object#clone()
1015
     */
1016
    @Override
1017
    public TaxonNode clone()  {
1018

    
1019
        try{
1020
            TaxonNode result = (TaxonNode)super.clone();
1021
            result.getTaxon().addTaxonNode(result);
1022

    
1023
            //childNodes
1024
            result.childNodes = new ArrayList<>();
1025
            result.countChildren = 0;
1026

    
1027
            //agents
1028
            result.agentRelations = new HashSet<>();
1029
            for (TaxonNodeAgentRelation rel : this.agentRelations){
1030
                result.addAgentRelation(rel.clone());
1031
            }
1032

    
1033
            //statusNote
1034
            result.statusNote = new HashMap<>();
1035
            for(Language lang : this.statusNote.keySet()){
1036
                result.statusNote.put(lang, this.statusNote.get(lang));
1037
            }
1038

    
1039
            return result;
1040
        }catch (CloneNotSupportedException e) {
1041
            logger.warn("Object does not implement cloneable");
1042
            e.printStackTrace();
1043
            return null;
1044
        }
1045
    }
1046

    
1047
//To be removed once SingleSourcedEntity attributes have been renamed to reference and microReference
1048

    
1049
    @Override
1050
    @Transient
1051
    public Reference getReference() {
1052
        return getCitation();
1053
    }
1054

    
1055
    @Override
1056
    @Transient
1057
    public String getMicroReference() {
1058
        return getCitationMicroReference();
1059
    }
1060

    
1061
}
(11-11/20)