new factory method for TextData
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / description / TextData.java
index 978ab5b6dc6da06df56fb01dacd8d1166d67fc73..8012a81225c3498f4262758fb39f2026ce5140d0 100644 (file)
@@ -1,27 +1,25 @@
 /**
 * Copyright (C) 2007 EDIT
-* European Distributed Institute of Taxonomy 
+* European Distributed Institute of Taxonomy
 * http://www.e-taxonomy.eu
-* 
+*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * See LICENSE.TXT at the top of this package for the full license terms.
 */
 
 package eu.etaxonomy.cdm.model.description;
 
-
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
-import eu.etaxonomy.cdm.jaxb.MultilanguageSetAdapter;
-import eu.etaxonomy.cdm.model.common.Language;
-import eu.etaxonomy.cdm.model.common.LanguageString;
-import eu.etaxonomy.cdm.model.common.MultilanguageSet;
-
-import org.apache.log4j.Logger;
-import org.hibernate.annotations.Cascade;
-import org.hibernate.annotations.CascadeType;
-import javax.persistence.*;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.ManyToOne;
+import javax.persistence.MapKeyJoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.Transient;
+import javax.validation.constraints.NotNull;
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
@@ -32,123 +30,425 @@ import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
+import org.apache.log4j.Logger;
+import org.hibernate.annotations.Cascade;
+import org.hibernate.annotations.CascadeType;
+import org.hibernate.envers.Audited;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.FieldBridge;
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Store;
+
+import eu.etaxonomy.cdm.hibernate.search.MultilanguageTextFieldBridge;
+import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
+import eu.etaxonomy.cdm.model.common.IMultiLanguageTextHolder;
+import eu.etaxonomy.cdm.model.common.Language;
+import eu.etaxonomy.cdm.model.common.LanguageString;
+import eu.etaxonomy.cdm.model.common.MultilanguageTextHelper;
+import eu.etaxonomy.cdm.model.common.TermBase;
+
+
 /**
+ * This class represents information pieces expressed in one or several natural
+ * languages (for the {@link Feature feature} "medical use" for instance).
+ * A {@link TextFormat format} used for structuring the text may also be stated.
+ * <P>
+ * This class corresponds partially to NaturalLanguageDescriptionType according
+ * to the SDD schema.
+ *
  * @author m.doering
  * @version 1.0
  * @created 08-Nov-2007 13:06:59
  */
 @XmlAccessorType(XmlAccessType.FIELD)
 @XmlType(name = "TextData", propOrder = {
-    "multiLanguageText",
+    "multilanguageText",
     "format"
 })
 @XmlRootElement(name = "TextData")
 @Entity
-public class TextData extends DescriptionElementBase {
-       
-       static Logger logger = Logger.getLogger(TextData.class);
-
-       //@XmlElement(name = "MultiLanguageText", type = MultilanguageSet.class)
-       @XmlElement(name = "MultiLanguageText")
-    @XmlJavaTypeAdapter(MultilanguageSetAdapter.class)
-       private Map<Language, LanguageString> multiLanguageText;
-       
-       @XmlElement(name = "Format")
-       @XmlIDREF
-       @XmlSchemaType(name = "IDREF")
-       private TextFormat format;
-       
-       public static TextData NewInstance(){
-               return new TextData();
-       }
-       
-       public static TextData NewInstance(Feature feature){
-               return new TextData(feature);
-       }
-       
-       public static TextData NewInstance(String text, Language language, TextFormat format){
-               TextData result =  new TextData();
-               result.putText(text, language);
-               result.setFormat(format);
-               return result;
-       }
-       
-       /**
-        * Constructor
-        */
-       public TextData(){
-               this(null);
-       }
-       
-       public TextData(Feature feature){
-               super(feature);
-               initTextSet();
-       }
-
-       /**
-        * @return
-        */
-       @OneToMany (fetch= FetchType.LAZY)
-       @MapKey(name="language")
-    @Cascade({CascadeType.SAVE_UPDATE})
-       public Map<Language, LanguageString> getMultilanguageText() {
-               initTextSet();
-               return multiLanguageText;
-       }
-       protected void setMultilanguageText(Map<Language, LanguageString> texts) {
-               this.multiLanguageText = texts;
-       }
-       @Transient 
-       public String getText(Language language) {
-               initTextSet();
-               LanguageString languageString = multiLanguageText.get(language);
-               if (languageString == null){
-                       return null;
-               }else{
-                       return languageString.getText();
-               }
-       }
-       
-       @Transient
-       public LanguageString putText(String text, Language language) {
-               initTextSet();
-               LanguageString result = this.multiLanguageText.put(language , LanguageString.NewInstance(text, language));
-               return (result == null ? null : result);
-       }
-       @Transient
-       public LanguageString putText(LanguageString languageString) {
-               initTextSet();
-               
-               if (languageString == null){
-                       return null;
-               }else{
-                       Language language = languageString.getLanguage();
-                       return this.multiLanguageText.put(language, languageString);
-               }
-       }
-       public LanguageString removeText(Language language) {
-               initTextSet();
-               return this.multiLanguageText.remove(language);
-       }
-       
-       private void initTextSet(){
-               if (multiLanguageText == null){
-                       multiLanguageText = MultilanguageSet.NewInstance();
-               }
-       }
-       
-       public int countLanguages(){
-               initTextSet();
-               return multiLanguageText.size();
-       }
-       
-
-       @ManyToOne
-       public TextFormat getFormat() {
-               return format;
-       }
-       public void setFormat(TextFormat format) {
-               this.format = format;
-       }
+@Audited
+@Indexed(index = "eu.etaxonomy.cdm.model.description.DescriptionElementBase")
+public class TextData extends DescriptionElementBase implements IMultiLanguageTextHolder, Cloneable{
+    private static final long serialVersionUID = -2165015581278282615L;
+    private static final Logger logger = Logger.getLogger(TextData.class);
+
+    //@XmlElement(name = "MultiLanguageText", type = MultilanguageText.class)
+    @XmlElement(name = "MultiLanguageText")
+    @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
+    @OneToMany (fetch= FetchType.LAZY, orphanRemoval=true)
+    @MapKeyJoinColumn(name="multilanguagetext_mapkey_id")
+    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
+    @Field(name="text", store=Store.YES)
+    @FieldBridge(impl=MultilanguageTextFieldBridge.class)
+    @NotNull
+    private Map<Language, LanguageString> multilanguageText = new HashMap<Language,LanguageString>();
+
+    @XmlElement(name = "Format")
+    @XmlIDREF
+    @XmlSchemaType(name = "IDREF")
+    @ManyToOne(fetch = FetchType.LAZY)
+    private TextFormat format;
+
+    @XmlTransient
+    @Transient
+    private boolean isHashMapHibernateBugFixed = false;
+
+    // ************* CONSTRUCTORS *************/
+    /**
+     * Class constructor: creates a new empty text data instance.
+     *
+     * @see #TextData(Feature)
+     */
+    public TextData(){
+        this(null);
+    }
+
+    /**
+     * Class constructor: creates a new text data instance with the {@link Feature feature}
+     * to be described.
+     *
+     * @param  feature the feature the text data refer to
+     * @see                    #TextData()
+     */
+    public TextData(Feature feature){
+        super(feature);
+    }
+
+    //********* METHODS **************************************/
+    /**
+     * Creates a new empty text data instance.
+     *
+     * @see #NewInstance(Feature)
+     * @see #NewInstance(String, Language, TextFormat)
+     * @see    #NewInstance(Feature, String, Language, TextFormat)
+     */
+    public static TextData NewInstance(){
+        return new TextData();
+    }
+
+    /**
+     * Creates a new text data instance with the {@link Feature feature}
+     * to be described.
+     *
+     * @param  feature the feature the text data refer to
+     * @see                    #NewInstance()
+     * @see                    #NewInstance(String, Language, TextFormat)
+     * @see                                    #NewInstance(Feature, String, Language, TextFormat)
+     */
+    public static TextData NewInstance(Feature feature){
+        return new TextData(feature);
+    }
+
+    /**
+     * Creates a new text data instance with a given text in a given particular
+     * {@link Language language} and with the given text format for structuring it.
+     *
+     * @param  text            the text string with the content of the description
+     * @param  language        the language in which the text string is formulated
+     * @param  format          the text format used to structure the text string
+     * @see                            #NewInstance()
+     * @see                            #NewInstance(Feature)
+     * @see                                    #NewInstance(Feature, String, Language, TextFormat)
+     */
+    public static TextData NewInstance(String text, Language language, TextFormat format){
+        TextData result =  new TextData();
+        result.putText(language, text);
+        result.setFormat(format);
+        return result;
+    }
+    
+    
+    /**
+     * Creates a new text data instance with a given text in a given particular
+     * {@link Language language} and with the given text format for structuring it.
+     *
+     * @param   feature            the feature the text data refer to
+     * @param  text            the text string with the content of the description
+     * @param  language        the language in which the text string is formulated
+     * @param  format          the text format used to structure the text string
+     * @see                            #NewInstance()
+     * @see                            #NewInstance(Feature)
+     * @see                                    #NewInstance(String, Language, TextFormat)
+     */
+    public static TextData NewInstance(Feature feature, String text, Language language, TextFormat format){
+        TextData result =  new TextData();
+        result.putText(language, text);
+        result.setFormat(format);
+        return result;
+    }
+
+    /**
+     * Returns a copy of the multilanguage text with the content of <i>this</i> text data.
+     * The different {@link LanguageString language strings} (texts) contained in the
+     * multilanguage text should all have the same meaning.
+     *
+     * @see    #getText(Language)
+     */
+    public Map<Language, LanguageString> getMultilanguageText() {
+        fixHashMapHibernateBug();
+
+//     HashMap<Language, LanguageString> result = new HashMap<Language, LanguageString>();
+//             result.putAll(multilanguageText);
+//             return result;
+        return multilanguageText;
+    }
+
+//    /**
+//     * Sets the multilanguage text.
+//      * The different {@link LanguageString language strings} (texts) contained in the
+//      * multilanguage text should all have the same meaning.
+//     *
+//     * @param multilanguageText
+//     */
+//    private void setMultilanguageText(Map<Language,LanguageString> multilanguageText) {
+//     this.multilanguageText = multilanguageText;
+//    }
+
+    /**
+     * Returns the multilanguage text with the content of <i>this</i> text data for
+     * a specific language.
+     *
+     * @param language the language in which the text string looked for is formulated
+     * @return
+     */
+    public LanguageString getLanguageText(Language language){
+        //work around for the problem that contains does not work correctly in persisted maps.
+        //This is because the persisted uuid is not present when loading the map key and
+        //therefore the hash code for language is not computed correctly
+        //see DescriptionElementDaoHibernateTest and #2114
+//     for (Map.Entry<Language, LanguageString> entry : multilanguageText.entrySet()){
+//             if (entry.getKey() != null){
+//                     if (entry.getKey().equals(language)){
+//                             return entry.getValue();
+//                     }
+//             }else{
+//                     if (language == null){
+//                             return entry.getValue();
+//                     }
+//             }
+//     }
+//     return null;
+        //old
+        return getMultilanguageText().get(language);
+    }
+
+    /**
+     * Returns the text string in the given {@link Language language} with the content
+     * of <i>this</i> text data.
+     *
+     * @param language the language in which the text string looked for is formulated
+     * @see                            #getMultilanguageText(Language)
+     */
+    public String getText(Language language) {
+        LanguageString languageString = getLanguageText(language);
+        if (languageString == null){
+            return null;
+        }else{
+            return languageString.getText();
+        }
+    }
+
+    /**
+     * Returns the LanguageString in the preferred language. Preferred languages
+     * are specified by the parameter languages, which receives a list of
+     * Language instances in the order of preference. If no representation in
+     * any preferred languages is found the method falls back to return the
+     * Representation in Language.DEFAULT() and if neccesary further falls back
+     * to return the first element found if any.
+     *
+     * TODO think about this fall-back strategy &
+     * see also {@link TermBase#getPreferredRepresentation(List)}
+     *
+     * @param languages
+     * @return
+     */
+    public LanguageString getPreferredLanguageString(List<Language> languages) {
+        return MultilanguageTextHelper.getPreferredLanguageString(getMultilanguageText(), languages);
+    }
+
+    private void fixHashMapHibernateBug() {
+        //workaround for key problem
+        if(! isHashMapHibernateBugFixed){
+            HashMap<Language, LanguageString> tmp = new HashMap<Language, LanguageString>();
+            tmp.putAll(multilanguageText);
+            multilanguageText.clear();
+            multilanguageText.putAll(tmp);
+
+            isHashMapHibernateBugFixed = true;
+        }
+    }
+
+    /**
+     * Creates a {@link LanguageString language string} based on the given text string
+     * and the given {@link Language language}, returns it and adds it to the multilanguage
+     * text representing the content of <i>this</i> text data.
+     *
+     * @param text             the string representing the content of the text data
+     *                                         in a particular language
+     * @param language the language in which the text string is formulated
+     * @see                            #getMultilanguageText()
+     * @see                            #putText(LanguageString)
+     * @return                 the previous language string associated with the given Language, or null if there was no mapping for the given Language
+     * @deprecated             should follow the put semantic of maps, this method will be removed in v4.0
+     *                                         Use the {@link #putText(Language, String) putText} method instead
+     */
+    @Deprecated
+    public LanguageString putText(String text, Language language) {
+        return this.putText(language, text);
+    }
+
+    /**
+     * Creates a {@link LanguageString language string} based on the given text string
+     * and the given {@link Language language}, returns it and adds it to the multilanguage
+     * text representing the content of <i>this</i> text data.
+     *
+     * @param language the language in which the text string is formulated
+     * @param text             the string representing the content of the text data
+     *                                         in a particular language
+     *
+     * @see                            #getMultilanguageText()
+     * @see                            #putText(LanguageString)
+     * @return                 the previous language string associated with the given Language, or null if there was no mapping for the given Language
+     */
+    public LanguageString putText(Language language, String text) {
+        fixHashMapHibernateBug();
+        //** end workaround
+        LanguageString languageString = multilanguageText.get(language);
+        if (languageString != null){
+            languageString.setText(text);
+        }else{
+            languageString = LanguageString.NewInstance(text, language);
+        }
+        LanguageString result = this.multilanguageText.put(language , languageString);
+        return (result == null ? null : result);
+    }
+
+
+    /**
+     * Adds a translated {@link LanguageString text in a particular language}
+     * to the multi-language text representing the content of <i>this</i> text data.
+     * The given language string will be returned.
+     *
+     * @param languageString   the language string representing the content of
+     *                                                         the text data in a particular language
+     * @see                                            #getMultilanguageText()
+     * @see                                            #putText(String, Language)
+     * @see                                            HashMap#put(Object, Object)
+     * @return                                 the previous language string associated with key, or null if there was no mapping for key
+     */
+    public LanguageString putText(LanguageString languageString) {
+
+        if (languageString == null){
+            return null;
+        }else{
+            Language language = languageString.getLanguage();
+            return this.multilanguageText.put(language, languageString);
+        }
+    }
+    /**
+     * Removes from the multilanguage representing the content of
+     * <i>this</i> text data the one {@link LanguageString language string}
+     * with the given {@link Language language}. Returns the removed
+     * language string.
+     *
+     * @param  language        the language in which the language string to be removed
+     *                                         has been formulated
+     * @return                 the language string associated with the given language or null if there was no mapping for the given Language
+     * @see                    #getMultilanguageText()
+     */
+    public LanguageString removeText(Language language) {
+        fixHashMapHibernateBug();
+        return this.multilanguageText.remove(language);
+    }
+
+    /**
+     * Returns the number of {@link Language languages} in which the content
+     * of <i>this</i> text data has been formulated.
+     *
+     * @see    #getMultilanguageText()
+     */
+    public int countLanguages(){
+        return multilanguageText.size();
+    }
+
+
+    /**
+     * Returns the {@link TextFormat format} used for structuring the text representing
+     * the content of <i>this</i> text data.
+     *
+     * @see    #getMultilanguageText()
+     */
+    public TextFormat getFormat() {
+        return format;
+    }
+    /**
+     * @see    #getFormat()
+     */
+    public void setFormat(TextFormat format) {
+        this.format = format;
+    }
+
+    /**
+     * @see {@link java.util.Map#containsKey(Object)}
+     * @param language
+     * @return
+     */
+    public boolean containsKey(Language language){
+        return getMultilanguageText().containsKey(language);
+    }
+
+    /**
+     * @see {@link java.util.Map#containsValue(Object)}
+     * @param languageString
+     * @return
+     */
+    public boolean containsValue(LanguageString languageString){
+        return getMultilanguageText().containsValue(languageString);
+    }
+
+
+    /**
+     * Returns the number of languages available for this text data.
+     * @see {@link java.util.Map#size()}
+     * @return
+     */
+    public int size(){
+        return this.multilanguageText.size();
+    }
+
+
+//*********************************** CLONE *****************************************/
+
+    /**
+     * Clones <i>this</i> text data. This is a shortcut that enables to create
+     * a new instance that differs only slightly from <i>this</i> text data by
+     * modifying only some of the attributes.
+     *
+     * @see eu.etaxonomy.cdm.model.description.DescriptionElementBase#clone()
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public Object clone() {
+
+        try {
+            TextData result = (TextData)super.clone();
+
+            //description
+            result.multilanguageText = new HashMap<Language, LanguageString>();
+            for (Language language : getMultilanguageText().keySet()){
+                //TODO clone needed? See also IndividualsAssociation
+                LanguageString newLanguageString = (LanguageString)getMultilanguageText().get(language).clone();
+                result.multilanguageText.put(language, newLanguageString);
+            }
+
+            return result;
+            //no changes to: format
+        } catch (CloneNotSupportedException e) {
+            logger.warn("Object does not implement cloneable");
+            e.printStackTrace();
+            return null;
+        }
+    }
 
 }
\ No newline at end of file