Project

General

Profile

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

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

    
13
import java.io.PrintStream;
14
import java.util.HashSet;
15
import java.util.Set;
16

    
17
import javax.persistence.Entity;
18
import javax.persistence.FetchType;
19
import javax.persistence.JoinColumn;
20
import javax.persistence.JoinTable;
21
import javax.persistence.ManyToMany;
22
import javax.persistence.OneToOne;
23
import javax.validation.constraints.NotNull;
24
import javax.xml.bind.annotation.XmlAccessType;
25
import javax.xml.bind.annotation.XmlAccessorType;
26
import javax.xml.bind.annotation.XmlElement;
27
import javax.xml.bind.annotation.XmlElementWrapper;
28
import javax.xml.bind.annotation.XmlIDREF;
29
import javax.xml.bind.annotation.XmlRootElement;
30
import javax.xml.bind.annotation.XmlSchemaType;
31
import javax.xml.bind.annotation.XmlType;
32

    
33
import org.apache.log4j.Logger;
34
import org.hibernate.annotations.Cascade;
35
import org.hibernate.annotations.CascadeType;
36
import org.hibernate.envers.Audited;
37
import org.hibernate.search.annotations.Indexed;
38
import org.hibernate.tool.hbm2x.StringUtils;
39

    
40
import eu.etaxonomy.cdm.common.CdmUtils;
41
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
42
import eu.etaxonomy.cdm.model.common.Language;
43
import eu.etaxonomy.cdm.model.location.NamedArea;
44
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
45
import eu.etaxonomy.cdm.model.taxon.Taxon;
46
import eu.etaxonomy.cdm.strategy.generate.PolytomousKeyGenerator;
47

    
48
/**
49
 * The class allowing the representation of single-access fixed dichotomous or
50
 * polytomous decision keys used to identify {@link SpecimenOrObservationBase
51
 * specimens or observations} (this means to assign {@link Taxon taxa} to). The
52
 * key may be written by an author or may be generated by the
53
 * {@link PolytomousKeyGenerator}. The different paths to the taxa are expressed
54
 * by a decision graph consisting of {@link PolytomousKeyNode
55
 * PolytomousKeyNodes}. The root node of such graph is accessible by
56
 * {@link #getRoot()}. Please refer to {@link PolytomousKeyNode} for detailed
57
 * documentation on this decision graph please
58
 * 
59
 * @author h.fradin
60
 * @created 13.08.2009
61
 * @version 1.0
62
 * 
63
 * @author a.mueller
64
 * @created 08.11.2010
65
 * @version 2.0
66
 */
67
@XmlAccessorType(XmlAccessType.FIELD)
68
@XmlType(name = "PolytomousKey", propOrder = { "coveredTaxa", "taxonomicScope",
69
		"geographicalScope", "scopeRestrictions", "root" })
70
@XmlRootElement(name = "PolytomousKey")
71
@Entity
72
@Indexed(index = "eu.etaxonomy.cdm.model.media.FeatureTree")
73
@Audited
74
public class PolytomousKey extends IdentifiableEntity implements IIdentificationKey {
75
	private static final long serialVersionUID = -3368243754557343942L;
76
	private static final Logger logger = Logger.getLogger(PolytomousKey.class);
77

    
78
	@XmlElementWrapper(name = "CoveredTaxa")
79
	@XmlElement(name = "CoveredTaxon")
80
	@XmlIDREF
81
	@XmlSchemaType(name = "IDREF")
82
	@ManyToMany(fetch = FetchType.LAZY)
83
	@NotNull
84
	@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
85
	private Set<Taxon> coveredTaxa = new HashSet<Taxon>();
86

    
87
	@XmlElementWrapper(name = "TaxonomicScope")
88
	@XmlElement(name = "Taxon")
89
	@XmlIDREF
90
	@XmlSchemaType(name = "IDREF")
91
	@ManyToMany(fetch = FetchType.LAZY)
92
	@JoinTable(name = "PolytomousKey_Taxon", joinColumns = @JoinColumn(name = "polytomousKey_id"), inverseJoinColumns = @JoinColumn(name = "taxon_id"))
93
	@NotNull
94
	@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
95
	private Set<Taxon> taxonomicScope = new HashSet<Taxon>();
96

    
97
	@XmlElementWrapper(name = "GeographicalScope")
98
	@XmlElement(name = "Area")
99
	@XmlIDREF
100
	@XmlSchemaType(name = "IDREF")
101
	@ManyToMany(fetch = FetchType.LAZY)
102
	@JoinTable(name = "PolytomousKey_NamedArea")
103
	@NotNull
104
	private Set<NamedArea> geographicalScope = new HashSet<NamedArea>();
105

    
106
	@XmlElementWrapper(name = "ScopeRestrictions")
107
	@XmlElement(name = "Restriction")
108
	@XmlIDREF
109
	@XmlSchemaType(name = "IDREF")
110
	@ManyToMany(fetch = FetchType.LAZY)
111
	@JoinTable(name = "PolytomousKey_Scope")
112
	@NotNull
113
	private Set<Scope> scopeRestrictions = new HashSet<Scope>();
114

    
115
	@XmlElement(name = "Root")
116
	@OneToOne(fetch = FetchType.LAZY)
117
	@Cascade({ CascadeType.SAVE_UPDATE, CascadeType.MERGE })
118
	private PolytomousKeyNode root;
119

    
120
	// ******************************** STATIC METHODS
121
	// ********************************/
122

    
123
	/**
124
	 * Creates a new empty identification multi-access key instance.
125
	 */
126
	public static PolytomousKey NewInstance() {
127
		return new PolytomousKey();
128
	}
129

    
130
	/**
131
	 * Creates a new empty identification polytomous key instance.
132
	 */
133
	public static PolytomousKey NewTitledInstance(String title) {
134
		PolytomousKey result = new PolytomousKey();
135
		result.setTitleCache(title, true);
136
		return result;
137
	}
138

    
139
	// ************************** CONSTRUCTOR
140
	// *******************************************/
141

    
142
	/**
143
	 * Class constructor: creates a new empty multi-access key instance.
144
	 */
145
	protected PolytomousKey() {
146
		super();
147
		root = PolytomousKeyNode.NewRootInstance();
148
		root.setKey(this);
149
	}
150

    
151
	// ************************ GETTER/ SETTER
152

    
153
	/**
154
	 * Returns the topmost {@link PolytomousKeyNode polytomous key node} (root
155
	 * node) of <i>this</i> polytomous key. The root node does not have any
156
	 * parent. Since polytomous key nodes recursively point to their child nodes
157
	 * the complete polytomous key is defined by its root node.
158
	 */
159
	public PolytomousKeyNode getRoot() {
160
		return root;
161
	}
162

    
163
	/**
164
	 * This method should be used by Hibernate only. If we want to make this
165
	 * method public we have to think about biderionality and also what should
166
	 * happen with the old root node.
167
	 * 
168
	 * @see #getRoot()
169
	 */
170
	public void setRoot(PolytomousKeyNode root) {
171
		this.root = root;
172
	}
173

    
174
	/**
175
	 * Returns the set of possible {@link Taxon taxa} corresponding to
176
	 * <i>this</i> identification key.
177
	 */
178
	public Set<Taxon> getCoveredTaxa() {
179
		if (coveredTaxa == null) {
180
			this.coveredTaxa = new HashSet<Taxon>();
181
		}
182
		return coveredTaxa;
183
	}
184

    
185
	/**
186
	 * @see #getCoveredTaxa()
187
	 */
188
	protected void setCoveredTaxa(Set<Taxon> coveredTaxa) {
189
		this.coveredTaxa = coveredTaxa;
190
	}
191

    
192
	/**
193
	 * Adds a {@link Taxon taxa} to the set of {@link #getCoveredTaxa() covered
194
	 * taxa} corresponding to <i>this</i> identification key.
195
	 * 
196
	 * @param taxon
197
	 *            the taxon to be added to <i>this</i> identification key
198
	 * @see #getCoveredTaxa()
199
	 */
200
	public void addCoveredTaxon(Taxon taxon) {
201
		this.coveredTaxa.add(taxon);
202
	}
203

    
204
	/**
205
	 * Removes one element from the set of {@link #getCoveredTaxa() covered
206
	 * taxa} corresponding to <i>this</i> identification key.
207
	 * 
208
	 * @param taxon
209
	 *            the taxon which should be removed
210
	 * @see #getCoveredTaxa()
211
	 * @see #addCoveredTaxon(Taxon)
212
	 */
213
	public void removeCoveredTaxon(Taxon taxon) {
214
		this.coveredTaxa.remove(taxon);
215
	}
216

    
217
	/**
218
	 * Returns the set of {@link NamedArea named areas} indicating the
219
	 * geospatial data where <i>this</i> identification key is valid.
220
	 */
221
	public Set<NamedArea> getGeographicalScope() {
222
		if (geographicalScope == null) {
223
			this.geographicalScope = new HashSet<NamedArea>();
224
		}
225
		return geographicalScope;
226
	}
227

    
228
	/**
229
	 * Adds a {@link NamedArea geoScope} to the set of {@link #getGeoScopes()
230
	 * geogspatial scopes} corresponding to <i>this</i> identification key.
231
	 * 
232
	 * @param geoScope
233
	 *            the named area to be added to <i>this</i> identification key
234
	 * @see #getGeoScopes()
235
	 */
236
	public void addGeographicalScope(NamedArea geoScope) {
237
		this.geographicalScope.add(geoScope);
238
	}
239

    
240
	/**
241
	 * Removes one element from the set of {@link #getGeoScopes() geogspatial
242
	 * scopes} corresponding to <i>this</i> identification key.
243
	 * 
244
	 * @param geoScope
245
	 *            the named area which should be removed
246
	 * @see #getGeoScopes()
247
	 * @see #addGeoScope(NamedArea)
248
	 */
249
	public void removeGeographicalScope(NamedArea geoScope) {
250
		this.geographicalScope.remove(geoScope);
251
	}
252

    
253
	/**
254
	 * Returns the set of {@link Taxon taxa} that define the taxonomic scope of
255
	 * <i>this</i> identification key
256
	 */
257
	public Set<Taxon> getTaxonomicScope() {
258
		if (taxonomicScope == null) {
259
			this.taxonomicScope = new HashSet<Taxon>();
260
		}
261
		return taxonomicScope;
262
	}
263

    
264
	/**
265
	 * Adds a {@link Taxon taxa} to the set of {@link #getTaxonomicScope()
266
	 * taxonomic scopes} corresponding to <i>this</i> identification key.
267
	 * 
268
	 * @param taxon
269
	 *            the taxon to be added to <i>this</i> identification key
270
	 * @see #getTaxonomicScope()
271
	 */
272
	public void addTaxonomicScope(Taxon taxon) {
273
		this.taxonomicScope.add(taxon);
274
	}
275

    
276
	/**
277
	 * Removes one element from the set of {@link #getTaxonomicScope() taxonomic
278
	 * scopes} corresponding to <i>this</i> identification key.
279
	 * 
280
	 * @param taxon
281
	 *            the taxon which should be removed
282
	 * @see #getTaxonomicScope()
283
	 * @see #addTaxonomicScope(Taxon)
284
	 */
285
	public void removeTaxonomicScope(Taxon taxon) {
286
		this.taxonomicScope.remove(taxon);
287
	}
288

    
289
	/**
290
	 * Returns the set of {@link Scope scope restrictions} corresponding to
291
	 * <i>this</i> identification key
292
	 */
293
	public Set<Scope> getScopeRestrictions() {
294
		if (scopeRestrictions == null) {
295
			this.scopeRestrictions = new HashSet<Scope>();
296
		}
297
		return scopeRestrictions;
298
	}
299

    
300
	/**
301
	 * Adds a {@link Scope scope restriction} to the set of
302
	 * {@link #getScopeRestrictions() scope restrictions} corresponding to
303
	 * <i>this</i> identification key.
304
	 * 
305
	 * @param scopeRestriction
306
	 *            the scope restriction to be added to <i>this</i>
307
	 *            identification key
308
	 * @see #getScopeRestrictions()
309
	 */
310
	public void addScopeRestriction(Scope scopeRestriction) {
311
		this.scopeRestrictions.add(scopeRestriction);
312
	}
313

    
314
	/**
315
	 * Removes one element from the set of {@link #getScopeRestrictions() scope
316
	 * restrictions} corresponding to <i>this</i> identification key.
317
	 * 
318
	 * @param scopeRestriction
319
	 *            the scope restriction which should be removed
320
	 * @see #getScopeRestrictions()
321
	 * @see #addScopeRestriction(Scope)
322
	 */
323
	public void removeScopeRestriction(Scope scopeRestriction) {
324
		this.scopeRestrictions.remove(scopeRestriction);
325
	}
326

    
327
	// ******************** toString *****************************************/
328

    
329
	private class IntegerObject {
330
		int number = 0;
331

    
332
		int inc() {
333
			return number++;
334
		};
335

    
336
		@Override
337
		public String toString() {
338
			return String.valueOf(number);
339
		}
340
	}
341

    
342
	public String print(PrintStream stream) {
343
		String title = this.getTitleCache() + "\n";
344
		String strPrint = title;
345

    
346
		if (stream != null) {
347
			stream.print(title);
348
		}
349

    
350
		PolytomousKeyNode root = this.getRoot();
351
		strPrint += printNode(root, null, "  ", stream);
352
		return strPrint;
353
	}
354

    
355
	/**
356
	 * TODO this is a preliminary implementation
357
	 * 
358
	 * @param node
359
	 * @param identation
360
	 * @param no
361
	 * @param myNumber
362
	 * @param stream
363
	 * @return
364
	 */
365
	private String printNode(PolytomousKeyNode node, PolytomousKeyNode parent2,
366
			String identation, PrintStream stream) {
367
		String separator = ", ";
368

    
369
		String result = identation + node.getNodeNumber() + ". ";
370
		if (node != null) {
371
			// key choice
372
			String question = null;
373
			String feature = null;
374
			if (node.getQuestion() != null) {
375
				question = node.getQuestion().getLabelText(Language.DEFAULT());
376
			}
377
			if (node.getFeature() != null) {
378
				feature = node.getFeature().getLabel(Language.DEFAULT());
379
			}
380
			result += CdmUtils.concat(" - ", question, feature) + "\n";
381
			;
382

    
383
			// Leads
384
			char nextCounter = 'a';
385
			for (PolytomousKeyNode child : node.getChildren()) {
386
				String leadNumber = String.valueOf(nextCounter++);
387
				if (child.getStatement() != null) {
388
					String statement = child.getStatement().getLabelText(
389
							Language.DEFAULT());
390
					result += identation + "  " + leadNumber + ") "
391
							+ (statement == null ? "" : (statement));
392
					result += " ... ";
393
					// child node
394
					if (!child.isLeaf()) {
395
						result += child.getNodeNumber() + separator;
396
					}
397
					// taxon
398
					if (child.getTaxon() != null) {
399
						String strTaxon = "";
400
						if (child.getTaxon().getName() != null) {
401
							strTaxon = child.getTaxon().getName()
402
									.getTitleCache();
403
						} else {
404
							strTaxon = child.getTaxon().getTitleCache();
405
						}
406
						result += strTaxon + separator;
407
					}
408
					// subkey
409
					if (child.getSubkey() != null) {
410
						String subkey = child.getSubkey().getTitleCache();
411
						result += subkey + separator;
412
					}
413
					// other node
414
					if (child.getOtherNode() != null) {
415
						PolytomousKeyNode otherNode = child.getOtherNode();
416
						String otherNodeString = null;
417
						if (child.getKey().equals(otherNode.getKey())) {
418
							otherNodeString = String.valueOf(otherNode
419
									.getNodeNumber());
420
						} else {
421
							otherNodeString = otherNode.getKey() + " "
422
									+ otherNode.getNodeNumber();
423
						}
424
						result += otherNodeString + separator;
425
					}
426

    
427
					result = StringUtils.chompLast(result, separator);
428
					result += "\n";
429
				}
430
			}
431

    
432
			if (stream != null) {
433
				stream.print(result);
434
			}
435
			for (PolytomousKeyNode child : node.getChildren()) {
436
				if (!child.isLeaf()) {
437
					result += printNode(child, node, identation + "", stream);
438
				}
439
			}
440
		}
441
		return result;
442
	}
443

    
444
	//
445
	// public List<PolytomousKeyNode> getChildren() {
446
	// return getRoot().getChildren();
447
	// }
448

    
449
	// *********************** CLONE
450
	// ********************************************************/
451

    
452
	/**
453
	 * Clones <i>this</i> PolytomousKey. This is a shortcut that enables to
454
	 * create a new instance that differs only slightly from <i>this</i>
455
	 * PolytomousKey by modifying only some of the attributes.
456
	 * 
457
	 * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#clone()
458
	 * @see java.lang.Object#clone()
459
	 */
460
	@Override
461
	public Object clone() {
462
		PolytomousKey result;
463

    
464
		try {
465
			result = (PolytomousKey) super.clone();
466

    
467
			result.coveredTaxa = new HashSet<Taxon>();
468
			for (Taxon taxon : this.coveredTaxa) {
469
				result.addCoveredTaxon(taxon);
470
			}
471

    
472
			result.geographicalScope = new HashSet<NamedArea>();
473
			for (NamedArea area : this.geographicalScope) {
474
				result.addGeographicalScope(area);
475
			}
476

    
477
			result.root = (PolytomousKeyNode) this.root.clone();
478

    
479
			result.scopeRestrictions = new HashSet<Scope>();
480
			for (Scope scope : this.scopeRestrictions) {
481
				result.addScopeRestriction(scope);
482
			}
483

    
484
			result.taxonomicScope = new HashSet<Taxon>();
485
			for (Taxon taxon : this.taxonomicScope) {
486
				result.addTaxonomicScope(taxon);
487
			}
488

    
489
			return result;
490

    
491
		} catch (CloneNotSupportedException e) {
492
			logger.warn("Object does not implement cloneable");
493
			e.printStackTrace();
494
			return null;
495
		}
496

    
497
	}
498

    
499
}
(20-20/40)