cleanup
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / taxon / Classification.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.cdm.model.taxon;
11
12 import java.util.ArrayList;
13 import java.util.HashMap;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19
20 import javax.persistence.Entity;
21 import javax.persistence.FetchType;
22 import javax.persistence.JoinColumn;
23 import javax.persistence.JoinTable;
24 import javax.persistence.ManyToMany;
25 import javax.persistence.ManyToOne;
26 import javax.persistence.MapKeyJoinColumn;
27 import javax.persistence.OneToMany;
28 import javax.persistence.OneToOne;
29 import javax.persistence.Transient;
30 import javax.xml.bind.annotation.XmlAccessType;
31 import javax.xml.bind.annotation.XmlAccessorType;
32 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 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
39
40 import org.apache.log4j.Logger;
41 import org.hibernate.annotations.Cascade;
42 import org.hibernate.annotations.CascadeType;
43 import org.hibernate.envers.Audited;
44 import org.hibernate.search.annotations.Indexed;
45 import org.hibernate.search.annotations.IndexedEmbedded;
46
47 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
48 import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
49 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
50 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
51 import eu.etaxonomy.cdm.model.common.Language;
52 import eu.etaxonomy.cdm.model.common.LanguageString;
53 import eu.etaxonomy.cdm.model.common.MultilanguageText;
54 import eu.etaxonomy.cdm.model.common.TimePeriod;
55 import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
56 import eu.etaxonomy.cdm.model.location.NamedArea;
57 import eu.etaxonomy.cdm.model.reference.Reference;
58 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
59
60 /**
61 * @author a.mueller
62 * @since 31.03.2009
63 */
64 @XmlAccessorType(XmlAccessType.FIELD)
65 @XmlType(name = "Classification", propOrder = {
66 "name",
67 "description",
68 "rootNode",
69 "reference",
70 "microReference",
71 "source",
72 "timeperiod",
73 "geoScopes"
74 })
75 @XmlRootElement(name = "Classification")
76 @Entity
77 @Audited
78 @Indexed(index = "eu.etaxonomy.cdm.model.taxon.Classification")
79 public class Classification
80 extends IdentifiableEntity<IIdentifiableEntityCacheStrategy<Classification>>
81 implements ITaxonTreeNode{
82
83 private static final long serialVersionUID = -753804821474209635L;
84 private static final Logger logger = Logger.getLogger(Classification.class);
85
86 @XmlElement(name = "Name")
87 @OneToOne(fetch = FetchType.LAZY)
88 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
89 @JoinColumn(name = "name_id", referencedColumnName = "id")
90 @IndexedEmbedded
91 private LanguageString name;
92
93 @XmlElement(name = "rootNode")
94 @XmlIDREF
95 @XmlSchemaType(name = "IDREF")
96 @OneToOne(fetch=FetchType.LAZY)
97 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
98 private TaxonNode rootNode;
99
100 @XmlElement(name = "reference")
101 @XmlIDREF
102 @XmlSchemaType(name = "IDREF")
103 @ManyToOne(fetch = FetchType.LAZY)
104 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
105 private Reference reference;
106
107 @XmlElement(name = "microReference")
108 private String microReference;
109
110 @XmlElement(name = "TimePeriod")
111 private TimePeriod timeperiod = TimePeriod.NewInstance();
112
113 @XmlElementWrapper( name = "GeoScopes")
114 @XmlElement( name = "GeoScope")
115 @XmlIDREF
116 @XmlSchemaType(name="IDREF")
117 @ManyToMany(fetch = FetchType.LAZY)
118 @JoinTable(name="Classification_GeoScope")
119 // @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE}) remove cascade #5755
120 private Set<NamedArea> geoScopes = new HashSet<>();
121
122 @XmlElement(name = "Description")
123 @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
124 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
125 @MapKeyJoinColumn(name="description_mapkey_id")
126 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE })
127 @JoinTable(name = "Classification_Description")
128 // @Field(name="text", store=Store.YES)
129 // @FieldBridge(impl=MultilanguageTextFieldBridge.class)
130 private Map<Language,LanguageString> description = new HashMap<>();
131
132 //the source for this single classification
133 @XmlElement(name = "source")
134 @XmlIDREF
135 @XmlSchemaType(name = "IDREF")
136 @OneToOne(fetch = FetchType.LAZY, orphanRemoval=true)
137 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
138 private IdentifiableSource source;
139
140
141 // /**
142 // * If this classification is an alternative classification for a subclassification in
143 // * an other classification(parent view),
144 // * the alternativeViewRoot is the connection node from this classification to the parent classification.
145 // * It replaces another node in the parent view.
146 // */
147 // private AlternativeViewRoot alternativeViewRoot;
148
149 // ********************** FACTORY METHODS *********************************************/
150
151 public static Classification NewInstance(String name){
152 return NewInstance(name, null, Language.DEFAULT());
153 }
154
155 public static Classification NewInstance(String name, Language language){
156 return NewInstance(name, null, language);
157 }
158
159 public static Classification NewInstance(String name, Reference reference){
160 return NewInstance(name, reference, Language.DEFAULT());
161 }
162
163 public static Classification NewInstance(String name, Reference reference, Language language){
164 return new Classification(name, reference, language);
165 }
166
167 // **************************** CONSTRUCTOR *********************************/
168
169 //for hibernate use only, protected required by Javassist
170 protected Classification(){super();}
171
172 protected Classification(String name, Reference reference, Language language){
173 this();
174 LanguageString langName = LanguageString.NewInstance(name, language);
175 setName(langName);
176 setReference(reference);
177 this.rootNode = new TaxonNode();
178 rootNode.setClassification(this);
179 }
180
181 //********************** xxxxxxxxxxxxx ******************************************/
182
183 /**
184 * Returns the topmost {@link TaxonNode taxon node} (root node) of <i>this</i>
185 * classification. The root node does not have any parent and no taxon. Since taxon nodes
186 * recursively point to their child nodes the complete classification is
187 * defined by its root node.
188 */
189 public TaxonNode getRootNode(){
190 return rootNode;
191 }
192
193 public void setRootNode(TaxonNode root){
194 this.rootNode = root;
195 }
196
197 @Override
198 public TaxonNode addChildNode(TaxonNode childNode, Reference citation, String microCitation) {
199 return addChildNode(childNode, rootNode.getCountChildren(), citation, microCitation);
200 }
201
202 @Override
203 public TaxonNode addChildNode(TaxonNode childNode, int index, Reference citation, String microCitation) {
204
205 childNode.setParentTreeNode(this.rootNode, index);
206
207 childNode.setCitation(citation);
208 childNode.setCitationMicroReference(microCitation);
209 // childNode.setSynonymToBeUsed(synonymToBeUsed);
210
211 return childNode;
212 }
213
214 @Override
215 public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
216 return addChildTaxon(taxon, rootNode.getCountChildren(), citation, microCitation);
217 }
218
219 @Override
220 public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
221 return addChildNode(new TaxonNode(taxon), index, DescriptionElementSource.NewPrimarySourceInstance(citation, microCitation));
222 }
223
224 @Override
225 public TaxonNode addChildTaxon(Taxon taxon, DescriptionElementSource source) {
226 return addChildTaxon(taxon, rootNode.getCountChildren(), source);
227 }
228
229 @Override
230 public TaxonNode addChildTaxon(Taxon taxon, int index, DescriptionElementSource source) {
231 return addChildNode(new TaxonNode(taxon), index, source);
232 }
233
234 @Override
235 public TaxonNode addChildNode(TaxonNode childNode, int index, DescriptionElementSource source) {
236 childNode.setParentTreeNode(this.rootNode, index);
237 childNode.setSource(source);
238
239 return childNode;
240 }
241
242 @Override
243 public boolean deleteChildNode(TaxonNode node) {
244 boolean result = removeChildNode(node);
245
246 if (node.hasTaxon()){
247 node.getTaxon().removeTaxonNode(node);
248 node.setTaxon(null);
249 }
250
251 ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
252 for (TaxonNode childNode : childNodes){
253 if (childNode != null){
254 node.deleteChildNode(childNode);
255 }
256 }
257 return result;
258 }
259
260 public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
261 boolean result = removeChildNode(node);
262 if (node.hasTaxon()){
263 node.getTaxon().removeTaxonNode(node);
264 node.setTaxon(null);
265 }
266
267 if (deleteChildren){
268 ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
269 for (TaxonNode childNode : childNodes){
270 node.deleteChildNode(childNode);
271 }
272 }
273 return result;
274 }
275
276 protected boolean removeChildNode(TaxonNode node){
277 boolean result = false;
278 rootNode = HibernateProxyHelper.deproxy(rootNode, TaxonNode.class);
279 if(!rootNode.getChildNodes().contains(node)){
280 throw new IllegalArgumentException("TaxonNode is a not a root node of this classification");
281 }
282 // rootNode = HibernateProxyHelper.deproxy(rootNode, TaxonNode.class);
283 result = rootNode.removeChildNode(node);
284
285 node.setParent(null);
286 node.setClassification(null);
287
288 return result;
289 }
290
291 public boolean removeRootNode(){
292 boolean result = false;
293
294 if (rootNode != null){
295 this.rootNode.setChildNodes(new ArrayList<TaxonNode>());
296 this.rootNode.setParent(null);
297 rootNode = null;
298 result = true;
299 }
300 return result;
301 }
302
303 /**
304 * Appends an existing topmost node to another node of this tree. The existing topmost node becomes
305 * an ordinary node.
306 * @param topmostNode
307 * @param otherNode
308 * @param ref
309 * @param microReference
310 * @throws IllegalArgumentException
311 */
312 public void makeTopmostNodeChildOfOtherNode(TaxonNode topmostNode, TaxonNode otherNode, Reference ref, String microReference)
313 throws IllegalArgumentException{
314 if (otherNode == null){
315 throw new NullPointerException("other node must not be null");
316 }
317 if (! getChildNodes().contains(topmostNode)){
318 throw new IllegalArgumentException("root node to be added as child must already be root node within this tree");
319 }
320 if (otherNode.getClassification() == null || ! otherNode.getClassification().equals(this)){
321 throw new IllegalArgumentException("other node must already be node within this tree");
322 }
323 if (otherNode.equals(topmostNode)){
324 throw new IllegalArgumentException("root node and other node must not be the same");
325 }
326 otherNode.addChildNode(topmostNode, ref, microReference);
327 }
328
329 /**
330 * Checks if the given taxon is part of <b>this</b> tree.
331 * @param taxon
332 * @return
333 */
334 public boolean isTaxonInTree(Taxon taxon){
335 taxon = HibernateProxyHelper.deproxy(taxon, Taxon.class);
336 return (getNode(taxon) != null);
337 }
338
339 /**
340 * Checks if the given taxon is part of <b>this</b> tree. If so the according TaxonNode is returned.
341 * Otherwise <code>null</code> is returned.
342 * @param taxon
343 * @return
344 */
345 public TaxonNode getNode(Taxon taxon){
346 if (taxon == null){
347 return null;
348 }
349
350 for (TaxonNode taxonNode: taxon.getTaxonNodes()){
351 Classification classification = deproxy(taxonNode.getClassification(), Classification.class);
352 if (classification.equals(this)){
353 return taxonNode;
354 }
355 }
356 return null;
357 }
358
359 /**
360 * Checks if the given taxon is one of the topmost taxa in <b>this</b> tree.
361 * @param taxon
362 * @return
363 */
364 public boolean isTopmostInTree(Taxon taxon){
365 return (getTopmostNode(taxon) != null);
366 }
367
368 /**
369 * Checks if the taxon is a direct child of the root of <b>this</b> tree and returns the according node if true.
370 * Returns <code>null</code> otherwise.
371 * @param taxon
372 * @return
373 */
374 public TaxonNode getTopmostNode(Taxon taxon){
375 if (taxon == null){
376 return null;
377 }
378 for (TaxonNode taxonNode: taxon.getTaxonNodes()){
379 if (taxonNode.getClassification().equals(this)){
380 if (this.getChildNodes().contains(taxonNode)){
381 if (taxonNode.getParent() == null){
382 logger.warn("A topmost node should always have the root node as parent but actually has no parent");
383 }else if (taxonNode.getParent().getParent() != null){
384 logger.warn("The root node should have not parent but actually has one");
385 }else if (taxonNode.getParent().getTaxon() != null){
386 logger.warn("The root node should have not taxon but actually has one");
387 }
388 return taxonNode;
389 }
390 }
391 }
392 return null;
393 }
394
395 private boolean handleCitationOverwrite(TaxonNode childNode, Reference citation, String microCitation){
396 if (citation != null){
397 if (childNode.getReference() != null && ! childNode.getReference().equals(citation)){
398 logger.warn("TaxonNode source will be overwritten");
399 }
400 childNode.setCitation(citation);
401 }
402 if (microCitation != null){
403 if (childNode.getMicroReference() != null && ! childNode.getMicroReference().equals(microCitation)){
404 logger.warn("TaxonNode source detail will be overwritten");
405 }
406 childNode.setCitationMicroReference(microCitation);
407 }
408 return true;
409 }
410
411 /**
412 * Relates two taxa as parent-child nodes within a classification. <BR>
413 * If the taxa are not yet part of the tree they are added to it.<Br>
414 * If the child taxon is a topmost node still it is added as child and deleted from the rootNode set.<Br>
415 * If the child is a child of another parent already an IllegalStateException is thrown because a child can have only
416 * one parent. <Br>
417 * If the parent-child relationship between these two taxa already exists nothing is changed. Only
418 * citation and microcitation are overwritten by the new values if these values are not null.
419 *
420 * @param parent
421 * @param child
422 * @param citation
423 * @param microCitation
424 * @return the childNode
425 * @throws IllegalStateException If the child is a child of another parent already
426 */
427 public TaxonNode addParentChild (Taxon parent, Taxon child, Reference citation, String microCitation)
428 throws IllegalStateException{
429 try {
430 if (parent == null || child == null){
431 logger.warn("Child or parent taxon is null.");
432 return null;
433 }
434 if (parent == child){
435 logger.warn("A taxon should never be its own child. Child not added");
436 return null;
437 }
438 TaxonNode parentNode = this.getNode(parent);
439 TaxonNode childNode = this.getNode(child);
440
441 //if child exists in tree and has a parent
442 //no multiple parents are allowed in the tree
443 if (childNode != null && ! childNode.isTopmostNode()){
444 //...different to the parent taxon throw exception
445 if ( !(childNode.getParent().getTaxon().equals(parent) )){
446 if (childNode.getParent().getTaxon().getId() != 0 && childNode.getParent().getTaxon().getName().equals(parent.getName())){
447 throw new IllegalStateException("The child taxon is already part of the tree but has an other parent taxon than the parent to be added. Child: " + child.toString() + ", new parent:" + parent.toString() + ", old parent: " + childNode.getParent().getTaxon().toString()) ;
448 }
449 //... same as the parent taxon do nothing but overwriting citation and microCitation
450 }else{
451 handleCitationOverwrite(childNode, citation, microCitation);
452 return childNode;
453 }
454 }
455
456 //add parent node if not exist
457 if (parentNode == null){
458 parentNode = this.addChildTaxon(parent, null, null);
459 }
460
461 //add child if not exists
462 if (childNode == null){
463 childNode = parentNode.addChildTaxon(child, citation, microCitation);
464 }else{
465 //child is still topmost node
466 //TODO test if child is topmostNode otherwise throw IllegalStateException
467 if (! this.isTopmostInTree(child)){
468 //throw new IllegalStateException("Child is not a topmost node but must be");
469 if (childNode.getClassification() != null && childNode.getParent() == null){
470 logger.warn("Child has no parent and is not a topmost node, child: " + child.getId() + " classification: " + childNode.getClassification().getId());
471 }else{
472 logger.warn("ChildNode has no classification: " + childNode.getId());
473 }
474 parentNode.addChildNode(childNode, citation, microCitation);
475 if (!parentNode.isTopmostNode()){
476 this.addChildNode(parentNode, citation, microCitation);
477 logger.warn("parent is added as a topmost node");
478 }else{
479 logger.warn("parent is already a topmost node");
480 }
481 }else{
482 this.makeTopmostNodeChildOfOtherNode(childNode, parentNode, citation, microCitation);
483 }
484 }
485 return childNode;
486 } catch (IllegalStateException e) {
487 throw e;
488 } catch (RuntimeException e){
489 throw e;
490 }
491 }
492
493 @Transient
494 public Reference getCitation() {
495 return reference;
496 }
497
498 public LanguageString getName() {
499 return name;
500 }
501 public void setName(LanguageString name) {
502 this.name = name;
503 }
504
505 /**
506 * Returns a set containing all nodes in this classification.
507 *
508 * Caution: Use this method with care. It can be very time and resource consuming and might
509 * run into OutOfMemoryExceptions for big trees.
510 *
511 * @return
512 */
513 @Transient
514 public Set<TaxonNode> getAllNodes() {
515 Set<TaxonNode> allNodes = new HashSet<>();
516
517 for(TaxonNode rootNode : getChildNodes()){
518 allNodes.addAll(rootNode.getDescendants());
519 }
520
521 return allNodes;
522 }
523
524 @Override
525 @Transient
526 public List<TaxonNode> getChildNodes() {
527 return rootNode.getChildNodes();
528 }
529
530 @Override
531 public Reference getReference() {
532 return reference;
533 }
534 public void setReference(Reference reference) {
535 this.reference = reference;
536 }
537
538 @Override
539 public String getMicroReference() {
540 return microReference;
541 }
542 public void setMicroReference(String microReference) {
543 this.microReference = microReference;
544 }
545
546 /**
547 * The point in time, the time period or the season for which this description element
548 * is valid. A season may be expressed by not filling the year part(s) of the time period.
549 */
550 public TimePeriod getTimeperiod() {
551 return timeperiod;
552 }
553 /**
554 * @see #getTimeperiod()
555 */
556 public void setTimeperiod(TimePeriod timeperiod) {
557 if (timeperiod == null){
558 timeperiod = TimePeriod.NewInstance();
559 }
560 this.timeperiod = timeperiod;
561 }
562
563 /**
564 * Returns the set of {@link NamedArea named areas} indicating the geospatial
565 * data where <i>this</i> {@link Classification} is valid.
566 */
567 public Set<NamedArea> getGeoScopes(){
568 return this.geoScopes;
569 }
570
571 /**
572 * Adds a {@link NamedArea named area} to the set of {@link #getGeoScopes() named areas}
573 * delimiting the geospatial area where <i>this</i> {@link Classification} is valid.
574 *
575 * @param geoScope the named area to be additionally assigned to <i>this</i> taxon description
576 * @see #getGeoScopes()
577 */
578 public void addGeoScope(NamedArea geoScope){
579 this.geoScopes.add(geoScope);
580 }
581
582 /**
583 * Removes one element from the set of {@link #getGeoScopes() named areas} delimiting
584 * the geospatial area where <i>this</i> {@link Classification} is valid.
585 *
586 * @param geoScope the named area which should be removed
587 * @see #getGeoScopes()
588 * @see #addGeoScope(NamedArea)
589 */
590 public void removeGeoScope(NamedArea geoScope){
591 this.geoScopes.remove(geoScope);
592 }
593
594 /**
595 * Returns the i18n description used to describe
596 * <i>this</i> {@link Classification}. The different {@link LanguageString language strings}
597 * contained in the multilanguage text should all have the same meaning.
598 */
599 public Map<Language,LanguageString> getDescription(){
600 return this.description;
601 }
602
603 /**
604 * Adds a translated {@link LanguageString text in a particular language}
605 * to the {@link MultilanguageText description} used to describe
606 * <i>this</i> {@link Classification}.
607 *
608 * @param description the language string describing the individuals association
609 * in a particular language
610 * @see #getDescription()
611 * @see #putDescription(Language, String)
612 *
613 */
614 public void putDescription(LanguageString description){
615 this.description.put(description.getLanguage(),description);
616 }
617 /**
618 * Creates a {@link LanguageString language string} based on the given text string
619 * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
620 * used to describe <i>this</i> {@link Classification}.
621 *
622 * @param text the string describing the individuals association
623 * in a particular language
624 * @param language the language in which the text string is formulated
625 * @see #getDescription()
626 * @see #putDescription(LanguageString)
627 */
628 public void putDescription(Language language, String text){
629 this.description.put(language, LanguageString.NewInstance(text, language));
630 }
631 /**
632 * Removes from the {@link MultilanguageText description} used to describe
633 * <i>this</i> {@link Classification} the one {@link LanguageString language string}
634 * with the given {@link Language language}.
635 *
636 * @param language the language in which the language string to be removed
637 * has been formulated
638 * @see #getDescription()
639 */
640 public void removeDescription(Language language){
641 this.description.remove(language);
642 }
643
644 @Override
645 public String generateTitle() {
646 //TODO implement as cache strategy
647 if (protectedTitleCache){
648 return this.titleCache;
649 }else if (name != null){
650 return name.getText();
651 }else if (reference != null){
652 return this.reference.getTitleCache();
653 }else{
654 return this.toString();
655 }
656 }
657
658 public int compareTo(Object o) {
659 //TODO needs to be implemented
660 return 0;
661 }
662
663 @Override
664 public boolean hasChildNodes() {
665 return getChildNodes().size() > 0;
666 }
667
668 //*********************** CLONE ********************************************************/
669 /**
670 * Clones <i>this</i> classification. This is a shortcut that enables to create
671 * a new instance that differs only slightly from <i>this</i> classification by
672 * modifying only some of the attributes.<BR><BR>
673
674 * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
675 * @see java.lang.Object#clone()
676 */
677 @Override
678 public Object clone() {
679 Classification result;
680 try{
681 result = (Classification)super.clone();
682 //result.rootNode.childNodes = new ArrayList<TaxonNode>();
683 List<TaxonNode> rootNodes = new ArrayList<>();
684 TaxonNode rootNodeClone;
685
686 rootNodes.addAll(rootNode.getChildNodes());
687 TaxonNode rootNode;
688 Iterator<TaxonNode> iterator = rootNodes.iterator();
689
690 result.description = cloneLanguageString(this.description);
691
692 while (iterator.hasNext()){
693 rootNode = iterator.next();
694 rootNodeClone = rootNode.cloneDescendants();
695 rootNodeClone.setClassification(result);
696 result.addChildNode(rootNodeClone, rootNode.getReference(), rootNode.getMicroReference());
697 rootNodeClone.setSynonymToBeUsed(rootNode.getSynonymToBeUsed());
698 }
699
700 //geo-scopes
701 result.geoScopes = new HashSet<>();
702 for (NamedArea namedArea : getGeoScopes()){
703 result.geoScopes.add(namedArea);
704 }
705
706 return result;
707
708 }catch (CloneNotSupportedException e) {
709 logger.warn("Object does not implement cloneable");
710 e.printStackTrace();
711 return null;
712 }
713 }
714 }