free text search: better querying for 'isNotNull' and code harmonization
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / description / TextData.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.description;
11
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Map;
16
17 import javax.persistence.Entity;
18 import javax.persistence.FetchType;
19 import javax.persistence.ManyToOne;
20 import javax.persistence.OneToMany;
21 import javax.persistence.Transient;
22 import javax.validation.constraints.NotNull;
23 import javax.xml.bind.annotation.XmlAccessType;
24 import javax.xml.bind.annotation.XmlAccessorType;
25 import javax.xml.bind.annotation.XmlElement;
26 import javax.xml.bind.annotation.XmlIDREF;
27 import javax.xml.bind.annotation.XmlRootElement;
28 import javax.xml.bind.annotation.XmlSchemaType;
29 import javax.xml.bind.annotation.XmlTransient;
30 import javax.xml.bind.annotation.XmlType;
31 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
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.Field;
38 import org.hibernate.search.annotations.FieldBridge;
39 import org.hibernate.search.annotations.Indexed;
40 import org.hibernate.search.annotations.IndexedEmbedded;
41 import org.hibernate.search.annotations.Store;
42
43 import eu.etaxonomy.cdm.hibernate.search.MultilanguageTextFieldBridge;
44 import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
45 import eu.etaxonomy.cdm.model.common.IMultiLanguageTextHolder;
46 import eu.etaxonomy.cdm.model.common.Language;
47 import eu.etaxonomy.cdm.model.common.LanguageString;
48 import eu.etaxonomy.cdm.model.common.MultilanguageTextHelper;
49 import eu.etaxonomy.cdm.model.common.TermBase;
50
51
52 /**
53 * This class represents information pieces expressed in one or several natural
54 * languages (for the {@link Feature feature} "medical use" for instance).
55 * A {@link TextFormat format} used for structuring the text may also be stated.
56 * <P>
57 * This class corresponds partially to NaturalLanguageDescriptionType according
58 * to the SDD schema.
59 *
60 * @author m.doering
61 * @version 1.0
62 * @created 08-Nov-2007 13:06:59
63 */
64 @XmlAccessorType(XmlAccessType.FIELD)
65 @XmlType(name = "TextData", propOrder = {
66 "multilanguageText",
67 "format"
68 })
69 @XmlRootElement(name = "TextData")
70 @Entity
71 @Audited
72 @Indexed(index = "eu.etaxonomy.cdm.model.description.DescriptionElementBase")
73 public class TextData extends DescriptionElementBase implements IMultiLanguageTextHolder, Cloneable{
74 private static final long serialVersionUID = -2165015581278282615L;
75 private static final Logger logger = Logger.getLogger(TextData.class);
76
77 //@XmlElement(name = "MultiLanguageText", type = MultilanguageText.class)
78 @XmlElement(name = "MultiLanguageText")
79 @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
80 @OneToMany (fetch= FetchType.LAZY)
81 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE, CascadeType.DELETE_ORPHAN })
82 @Field(name="text", store=Store.YES)
83 @FieldBridge(impl=MultilanguageTextFieldBridge.class)
84 @NotNull
85 private Map<Language, LanguageString> multilanguageText = new HashMap<Language,LanguageString>();
86
87 @XmlElement(name = "Format")
88 @XmlIDREF
89 @XmlSchemaType(name = "IDREF")
90 @ManyToOne(fetch = FetchType.LAZY)
91 private TextFormat format;
92
93 @XmlTransient
94 @Transient
95 private boolean isHashMapHibernateBugFixed = false;
96
97 // ************* CONSTRUCTORS *************/
98 /**
99 * Class constructor: creates a new empty text data instance.
100 *
101 * @see #TextData(Feature)
102 */
103 public TextData(){
104 this(null);
105 }
106
107 /**
108 * Class constructor: creates a new text data instance with the {@link Feature feature}
109 * to be described.
110 *
111 * @param feature the feature the text data refer to
112 * @see #TextData()
113 */
114 public TextData(Feature feature){
115 super(feature);
116 }
117
118 //********* METHODS **************************************/
119 /**
120 * Creates a new empty text data instance.
121 *
122 * @see #NewInstance(Feature)
123 * @see #NewInstance(String, Language, TextFormat)
124 */
125 public static TextData NewInstance(){
126 return new TextData();
127 }
128
129 /**
130 * Creates a new text data instance with the {@link Feature feature}
131 * to be described.
132 *
133 * @param feature the feature the text data refer to
134 * @see #NewInstance()
135 * @see #NewInstance(String, Language, TextFormat)
136 */
137 public static TextData NewInstance(Feature feature){
138 return new TextData(feature);
139 }
140
141 /**
142 * Creates a new text data instance with a given text in a given particular
143 * {@link Language language} and with the given text format for structuring it.
144 *
145 * @param text the text string with the content of the description
146 * @param language the language in which the text string is formulated
147 * @param format the text format used to structure the text string
148 * @see #NewInstance()
149 * @see #NewInstance(Feature)
150 */
151 public static TextData NewInstance(String text, Language language, TextFormat format){
152 TextData result = new TextData();
153 result.putText(language, text);
154 result.setFormat(format);
155 return result;
156 }
157
158 /**
159 * Returns a copy of the multilanguage text with the content of <i>this</i> text data.
160 * The different {@link LanguageString language strings} (texts) contained in the
161 * multilanguage text should all have the same meaning.
162 *
163 * @see #getText(Language)
164 */
165 public Map<Language, LanguageString> getMultilanguageText() {
166 fixHashMapHibernateBug();
167
168 // HashMap<Language, LanguageString> result = new HashMap<Language, LanguageString>();
169 // result.putAll(multilanguageText);
170 // return result;
171 return multilanguageText;
172 }
173
174 // /**
175 // * Sets the multilanguage text.
176 // * The different {@link LanguageString language strings} (texts) contained in the
177 // * multilanguage text should all have the same meaning.
178 // *
179 // * @param multilanguageText
180 // */
181 // private void setMultilanguageText(Map<Language,LanguageString> multilanguageText) {
182 // this.multilanguageText = multilanguageText;
183 // }
184
185 /**
186 * Returns the multilanguage text with the content of <i>this</i> text data for
187 * a specific language.
188 *
189 * @param language the language in which the text string looked for is formulated
190 * @return
191 */
192 public LanguageString getLanguageText(Language language){
193 //work around for the problem that contains does not work correctly in persisted maps.
194 //This is because the persisted uuid is not present when loading the map key and
195 //therefore the hash code for language is not computed correctly
196 //see DescriptionElementDaoHibernateTest and #2114
197 // for (Map.Entry<Language, LanguageString> entry : multilanguageText.entrySet()){
198 // if (entry.getKey() != null){
199 // if (entry.getKey().equals(language)){
200 // return entry.getValue();
201 // }
202 // }else{
203 // if (language == null){
204 // return entry.getValue();
205 // }
206 // }
207 // }
208 // return null;
209 //old
210 return getMultilanguageText().get(language);
211 }
212
213 /**
214 * Returns the text string in the given {@link Language language} with the content
215 * of <i>this</i> text data.
216 *
217 * @param language the language in which the text string looked for is formulated
218 * @see #getMultilanguageText(Language)
219 */
220 public String getText(Language language) {
221 LanguageString languageString = getLanguageText(language);
222 if (languageString == null){
223 return null;
224 }else{
225 return languageString.getText();
226 }
227 }
228
229 /**
230 * Returns the LanguageString in the preferred language. Preferred languages
231 * are specified by the parameter languages, which receives a list of
232 * Language instances in the order of preference. If no representation in
233 * any preferred languages is found the method falls back to return the
234 * Representation in Language.DEFAULT() and if neccesary further falls back
235 * to return the first element found if any.
236 *
237 * TODO think about this fall-back strategy &
238 * see also {@link TermBase#getPreferredRepresentation(List)}
239 *
240 * @param languages
241 * @return
242 */
243 public LanguageString getPreferredLanguageString(List<Language> languages) {
244 return MultilanguageTextHelper.getPreferredLanguageString(getMultilanguageText(), languages);
245 }
246
247 private void fixHashMapHibernateBug() {
248 //workaround for key problem
249 if(! isHashMapHibernateBugFixed){
250 HashMap<Language, LanguageString> tmp = new HashMap<Language, LanguageString>();
251 tmp.putAll(multilanguageText);
252 multilanguageText.clear();
253 multilanguageText.putAll(tmp);
254
255 isHashMapHibernateBugFixed = true;
256 }
257 }
258
259 /**
260 * Creates a {@link LanguageString language string} based on the given text string
261 * and the given {@link Language language}, returns it and adds it to the multilanguage
262 * text representing the content of <i>this</i> text data.
263 *
264 * @param text the string representing the content of the text data
265 * in a particular language
266 * @param language the language in which the text string is formulated
267 * @see #getMultilanguageText()
268 * @see #putText(LanguageString)
269 * @return the previous language string associated with the given Language, or null if there was no mapping for the given Language
270 * @deprecated should follow the put semantic of maps, this method will be removed in v4.0
271 * Use the {@link #putText(Language, String) putText} method instead
272 */
273 @Deprecated
274 public LanguageString putText(String text, Language language) {
275 return this.putText(language, text);
276 }
277
278 /**
279 * Creates a {@link LanguageString language string} based on the given text string
280 * and the given {@link Language language}, returns it and adds it to the multilanguage
281 * text representing the content of <i>this</i> text data.
282 *
283 * @param language the language in which the text string is formulated
284 * @param text the string representing the content of the text data
285 * in a particular language
286 *
287 * @see #getMultilanguageText()
288 * @see #putText(LanguageString)
289 * @return the previous language string associated with the given Language, or null if there was no mapping for the given Language
290 */
291 public LanguageString putText(Language language, String text) {
292 fixHashMapHibernateBug();
293 //** end workaround
294 LanguageString languageString = multilanguageText.get(language);
295 if (languageString != null){
296 languageString.setText(text);
297 }else{
298 languageString = LanguageString.NewInstance(text, language);
299 }
300 LanguageString result = this.multilanguageText.put(language , languageString);
301 return (result == null ? null : result);
302 }
303
304
305 /**
306 * Adds a translated {@link LanguageString text in a particular language}
307 * to the multi-language text representing the content of <i>this</i> text data.
308 * The given language string will be returned.
309 *
310 * @param languageString the language string representing the content of
311 * the text data in a particular language
312 * @see #getMultilanguageText()
313 * @see #putText(String, Language)
314 * @see HashMap#put(Object, Object)
315 * @return the previous language string associated with key, or null if there was no mapping for key
316 */
317 public LanguageString putText(LanguageString languageString) {
318
319 if (languageString == null){
320 return null;
321 }else{
322 Language language = languageString.getLanguage();
323 return this.multilanguageText.put(language, languageString);
324 }
325 }
326 /**
327 * Removes from the multilanguage representing the content of
328 * <i>this</i> text data the one {@link LanguageString language string}
329 * with the given {@link Language language}. Returns the removed
330 * language string.
331 *
332 * @param language the language in which the language string to be removed
333 * has been formulated
334 * @return the language string associated with the given language or null if there was no mapping for the given Language
335 * @see #getMultilanguageText()
336 */
337 public LanguageString removeText(Language language) {
338 fixHashMapHibernateBug();
339 return this.multilanguageText.remove(language);
340 }
341
342 /**
343 * Returns the number of {@link Language languages} in which the content
344 * of <i>this</i> text data has been formulated.
345 *
346 * @see #getMultilanguageText()
347 */
348 public int countLanguages(){
349 return multilanguageText.size();
350 }
351
352
353 /**
354 * Returns the {@link TextFormat format} used for structuring the text representing
355 * the content of <i>this</i> text data.
356 *
357 * @see #getMultilanguageText()
358 */
359 public TextFormat getFormat() {
360 return format;
361 }
362 /**
363 * @see #getFormat()
364 */
365 public void setFormat(TextFormat format) {
366 this.format = format;
367 }
368
369 /**
370 * @see {@link java.util.Map#containsKey(Object)}
371 * @param language
372 * @return
373 */
374 public boolean containsKey(Language language){
375 return getMultilanguageText().containsKey(language);
376 }
377
378 /**
379 * @see {@link java.util.Map#containsValue(Object)}
380 * @param languageString
381 * @return
382 */
383 public boolean containsValue(LanguageString languageString){
384 return getMultilanguageText().containsValue(languageString);
385 }
386
387
388 /**
389 * Returns the number of languages available for this text data.
390 * @see {@link java.util.Map#size()}
391 * @return
392 */
393 public int size(){
394 return this.multilanguageText.size();
395 }
396
397
398 //*********************************** CLONE *****************************************/
399
400 /**
401 * Clones <i>this</i> text data. This is a shortcut that enables to create
402 * a new instance that differs only slightly from <i>this</i> text data by
403 * modifying only some of the attributes.
404 *
405 * @see eu.etaxonomy.cdm.model.description.DescriptionElementBase#clone()
406 * @see java.lang.Object#clone()
407 */
408 @Override
409 public Object clone() {
410
411 try {
412 TextData result = (TextData)super.clone();
413
414 //description
415 result.multilanguageText = new HashMap<Language, LanguageString>();
416 for (Language language : getMultilanguageText().keySet()){
417 //TODO clone needed? See also IndividualsAssociation
418 LanguageString newLanguageString = (LanguageString)getMultilanguageText().get(language).clone();
419 result.multilanguageText.put(language, newLanguageString);
420 }
421
422 return result;
423 //no changes to: format
424 } catch (CloneNotSupportedException e) {
425 logger.warn("Object does not implement cloneable");
426 e.printStackTrace();
427 return null;
428 }
429 }
430
431 }