9c96021927b41e267393ef53751d16acb7b762c4
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / term / OrderedTermBase.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 package eu.etaxonomy.cdm.model.term;
10
11 import java.util.UUID;
12
13 import javax.persistence.Entity;
14 import javax.persistence.Transient;
15 import javax.xml.bind.annotation.XmlAccessType;
16 import javax.xml.bind.annotation.XmlAccessorType;
17 import javax.xml.bind.annotation.XmlElement;
18 import javax.xml.bind.annotation.XmlSeeAlso;
19 import javax.xml.bind.annotation.XmlType;
20
21 import org.apache.logging.log4j.LogManager;
22 import org.apache.logging.log4j.Logger;
23 import org.hibernate.envers.Audited;
24
25 import eu.etaxonomy.cdm.common.CdmUtils;
26 import eu.etaxonomy.cdm.model.common.CdmBase;
27 import eu.etaxonomy.cdm.model.common.RelationshipTermBase;
28 import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
29 import eu.etaxonomy.cdm.model.description.State;
30 import eu.etaxonomy.cdm.model.location.NamedArea;
31 import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
32 import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
33 import eu.etaxonomy.cdm.model.name.Rank;
34
35 /**
36 * @author m.doering
37 * @since 08-Nov-2007 13:06:23
38 */
39 @XmlAccessorType(XmlAccessType.FIELD)
40 @XmlType(name = "OrderedTermBase", propOrder = {
41 "orderIndex"
42 })
43 @XmlSeeAlso({
44 RelationshipTermBase.class,
45 PresenceAbsenceTerm.class,
46 State.class,
47 NamedArea.class,
48 NamedAreaLevel.class,
49 NomenclaturalStatusType.class,
50 Rank.class
51 })
52 @Entity
53 @Audited
54 public abstract class OrderedTermBase<T extends OrderedTermBase<T>>
55 extends DefinedTermBase<T> {
56
57 private static final long serialVersionUID = 8000797926720467399L;
58 @SuppressWarnings("unused")
59 private static final Logger logger = LogManager.getLogger(OrderedTermBase.class);
60
61 //Order index, value < 1 means that this Term is not in order yet
62 @XmlElement(name = "OrderIndex")
63 protected int orderIndex;
64
65 /**
66 * Higher ordered terms have a lower order index,
67 * lower ordered terms have a higher order index:
68 * <p>
69 * <b>a.oderIndex &lt; b.oderIndex : a &gt; b</b>
70 * @return the order index of a term
71 */
72 public int getOrderIndex() {
73 return orderIndex;
74 }
75
76 // *********************** CONSTRUCTOR *************************/
77
78 //for hibernate use only, protected required by bytebuddy and subclasses outside package
79 @Deprecated
80 protected OrderedTermBase(){}
81
82 protected OrderedTermBase(TermType type) {
83 super(type);
84 }
85 public OrderedTermBase(TermType type, String description, String label, String labelAbbrev) {
86 super(type, description, label, labelAbbrev);
87 }
88
89 // **************************** METHODS ******************************/
90
91 /**
92 * Compares this OrderedTermBase with the specified OrderedTermBase for
93 * order. Returns a -1, 0, or +1 if the orderIndex of this object is greater
94 * than, equal to, or less than the specified object. In case the parameter
95 * is <code>null</code> the
96 * <p>
97 * <b>Note:</b> The compare logic of this method might appear to be <b>inverse</b>
98 * to the one mentioned in
99 * {@link java.lang.Comparable#compareTo(java.lang.Object)}. This is, because the logic here
100 * is that the lower the orderIndex the higher the term. E.g. the very high {@link Rank}
101 * Kingdom may have an orderIndex close to 1.
102 *
103 * @param orderedTerm
104 * the OrderedTermBase to be compared
105 * @throws NullPointerException
106 * if the specified object is null
107 */
108 @Override
109 public int compareTo(T orderedTerm) {
110 return performCompareTo(orderedTerm, false);
111 }
112
113 /**
114 * Compares this {@link OrderedTermBase ordered term} with the given {@link OrderedTermBase thatTerm} for
115 * order. Returns a -1, 0, or +1 if the orderId of this object is greater
116 * than, equal to, or less than the specified object.
117 * <p>
118 * <b>Note:</b> The compare logic of this method is the <b>inverse logic</b>
119 * of the the one implemented in
120 * {@link java.lang.Comparable#compareTo(java.lang.Object)}
121 *
122 * @param orderedTerm
123 * the OrderedTermBase to be compared
124 * @param skipVocabularyCheck
125 * whether to skip checking if both terms to compare are in the
126 * same vocabulary
127 * @throws NullPointerException
128 * if the specified object is null
129 */
130 protected int performCompareTo(T thatTerm , boolean skipVocabularyCheck ) {
131
132 T thatTermLocal = CdmBase.deproxy(thatTerm);
133 if(!skipVocabularyCheck){
134 if (this.vocabulary == null || thatTermLocal.vocabulary == null){
135 throw new IllegalStateException("An ordered term (" + this.toString() + " or " + thatTermLocal.toString() + ") of class " + this.getClass() + " or " + thatTermLocal.getClass() + " does not belong to a vocabulary and therefore can not be compared");
136 }
137 if (! this.getVocabulary().getUuid().equals(thatTermLocal.vocabulary.getUuid())){
138 throw new IllegalStateException("2 terms do not belong to the same vocabulary and therefore can not be compared: " + this.getTitleCache() + " and " + thatTermLocal.getTitleCache());
139 }
140 }
141
142 int vocCompare = compareVocabularies(thatTermLocal);
143 if (vocCompare != 0){
144 return vocCompare;
145 }
146
147 int orderThat;
148 int orderThis;
149 try {
150 orderThat = thatTermLocal.orderIndex;
151 orderThis = orderIndex;
152 } catch (RuntimeException e) {
153 throw e;
154 }
155 if (orderThis > orderThat){
156 return -1;
157 }else if (orderThis < orderThat){
158 return 1;
159 }else {
160 if (skipVocabularyCheck){
161 String errorStr = "The term %s (ID: %s) is not attached to any vocabulary. This should not happen. "
162 + "Please add the term to an vocabulary";
163 if (this.vocabulary == null){
164 throw new IllegalStateException(String.format(errorStr, this.getLabel(), String.valueOf(this.getId())));
165 }else if (thatTermLocal.vocabulary == null){
166 throw new IllegalStateException(String.format(errorStr, thatTermLocal.getLabel(), String.valueOf(thatTermLocal.getId())));
167 }
168 }
169 return 0;
170 }
171 }
172
173 protected int compareVocabularies(T thatTerm) {
174 //if vocabularies are not equal order by voc.uuid to get a defined behavior
175 //ordering terms from 2 different vocabularies is generally not recommended
176 UUID thisVocUuid = this.vocabulary == null? null:this.vocabulary.getUuid();
177 UUID thatVocUuid = thatTerm.getVocabulary() == null? null:thatTerm.getVocabulary().getUuid();
178 int vocCompare = CdmUtils.nullSafeCompareTo(thisVocUuid, thatVocUuid);
179 return vocCompare;
180 }
181
182 /**
183 * If this term is lower than the parameter term, true is returned, else false.
184 * If the parameter term is null, an Exception is thrown.
185 * @param orderedTerm
186 * @return boolean result of the comparison
187 */
188 public boolean isLower(T orderedTerm){
189 return (this.compareTo(orderedTerm) < 0 );
190 }
191
192 /**
193 * If this term is higher than the parameter term, true is returned, else false.
194 * If the parameter term is null, an Exception is thrown.
195 * @param orderedTerm
196 * @return boolean result of the comparison
197 */
198 public boolean isHigher(T orderedTerm){
199 return (this.compareTo(orderedTerm) > 0 );
200 }
201
202 /**
203 * @deprecated To be used only by OrderedTermVocabulary
204 **/
205 @Deprecated
206 protected boolean decreaseIndex(OrderedTermVocabulary vocabulary){
207 if (vocabulary.indexChangeAllowed(this) == true){
208 orderIndex--;
209 return true;
210 }else{
211 return false;
212 }
213 }
214
215 /**
216 * @deprecated To be used only by OrderedTermVocabulary
217 **/
218 @Deprecated
219 protected boolean incrementIndex(OrderedTermVocabulary vocabulary){
220 if (vocabulary.indexChangeAllowed(this) == true){
221 orderIndex++;
222 return true;
223 }else{
224 return false;
225 }
226 }
227
228
229 @SuppressWarnings("unchecked")
230 @Transient
231 public T getNextHigherTerm(){ //#3327
232 if (getVocabulary() == null){
233 return null;
234 }else{
235 OrderedTermBase<T> result = CdmBase.deproxy(getVocabulary(), OrderedTermVocabulary.class).getNextHigherTerm(this);
236 return (T)result;
237 }
238 }
239
240 @SuppressWarnings("unchecked")
241 @Transient
242 public T getNextLowerTerm(){ //#3327
243 if (getVocabulary() == null){
244 return null;
245 }else{
246 OrderedTermBase<T> result = CdmBase.deproxy(getVocabulary(), OrderedTermVocabulary.class).getNextLowerTerm(this);
247 return (T)result;
248 }
249 }
250
251 //*********************** CLONE ********************************************************/
252
253 /**
254 * Clones <i>this</i> OrderedTermBase. This is a shortcut that enables to create
255 * a new instance that differs only slightly from <i>this</i> OrderedTermBase.
256 *
257 * @see eu.etaxonomy.cdm.model.term.DefinedTermBase#clone()
258 * @see java.lang.Object#clone()
259 */
260 @Override
261 public OrderedTermBase<T> clone() {
262 OrderedTermBase<T> result = (OrderedTermBase<T>) super.clone();
263 //no changes to orderIndex
264 return result;
265 }
266 }