Project

General

Profile

Revision 4cd47320

ID4cd473206ecc56a523020c28166c0750afc1484f
Parent 2211afa4
Child 70f7cd1e

Added by Andreas Müller about 2 years ago

fix #6557 implement basionym relation creator

View differences:

cdmlib-commons/src/main/java/eu/etaxonomy/cdm/common/CdmUtils.java
564 564
        return text;
565 565
    }
566 566

  
567
    /**
568
     * Compares 2 strings. If they are not empty and equal returns <code>true</code>
569
     * otherwise false.
570
     *
571
     * @param str1
572
     * @param str2
573
     * @return compare result as boolean
574
     */
575
    public static boolean nonEmptyEquals(String str1, String str2) {
576
        return (isNotBlank(str1) && str1.equals(str2));
577
    }
578

  
567 579
}
cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/name/ITaxonNameBase.java
694 694
     * @see  #isInfraGeneric()
695 695
     * @see  #isSpecies()
696 696
     * @see  #isInfraSpecific()
697
     * @see  #isGenusOrSupraGeneric()
697 698
     */
698 699
    public boolean isSupraGeneric();
699 700

  
......
706 707
     * @see  #isInfraGeneric()
707 708
     * @see  #isSpecies()
708 709
     * @see  #isInfraSpecific()
710
     * @see  #isGenusOrSupraGeneric()
709 711
     */
710 712
    public boolean isGenus();
711 713

  
714

  
715
    /**
716
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
717
     * taxon name is the genus rank or higher (<code>true</code>) or not (<code>false</code>).
718
     * Non viral names with
719
     * genus rank or higher are monomials. Returns false if rank is null.<BR>
720
     * This is a shortcut for {@link #isGenus()} || {@link #isSupraGeneric()}
721
     *
722
     * @see  #isGenus()
723
     * @see  #isSupraGeneric()
724
     * @see  #isInfraGeneric()
725
     * @see  #isSpecies()
726
     * @see  #isInfraSpecific()
727
     */
728
    boolean isGenusOrSupraGeneric();
729

  
712 730
    /**
713 731
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
714 732
     * taxon name is higher than the species rank and lower than the
......
808 826
    public int compareToName(TaxonNameBase<?,?> otherName);
809 827

  
810 828

  
829

  
811 830
}
cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/name/TaxonNameBase.java
2977 2977
        }
2978 2978
        return getRank().isGenus();
2979 2979
    }
2980

  
2981
    @Override
2982
    @Transient
2983
    public boolean isGenusOrSupraGeneric() {
2984
        return isGenus()|| isSupraGeneric();
2985
    }
2980 2986
    /**
2981 2987
     * Returns the boolean value indicating whether the taxonomic {@link Rank rank} of <i>this</i>
2982 2988
     * taxon name is higher than the species rank and lower than the
cdmlib-model/src/main/java/eu/etaxonomy/cdm/strategy/homotypicgroup/BasionymRelationCreator.java
1
// $Id$
2
/**
3
* Copyright (C) 2017 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
package eu.etaxonomy.cdm.strategy.homotypicgroup;
11

  
12
import java.util.ArrayList;
13
import java.util.List;
14
import java.util.Set;
15
import java.util.UUID;
16

  
17
import org.apache.log4j.Logger;
18

  
19
import eu.etaxonomy.cdm.common.CdmUtils;
20
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
21
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
22
import eu.etaxonomy.cdm.model.taxon.Synonym;
23
import eu.etaxonomy.cdm.model.taxon.SynonymType;
24
import eu.etaxonomy.cdm.model.taxon.Taxon;
25
import eu.etaxonomy.cdm.strategy.StrategyBase;
26

  
27
/**
28
 * This class tries to guess all basionym relationships for the synonyms of a given taxon
29
 * by evaluating the name parts including authors.
30
 * It adds all {@link TaxonNameBase taxon names} that seem to belong to the same
31
 * basionym to the homotypic group of this basionym and creates the basionym relationship
32
 * if not yet added/created.<BR>
33
 * Also it changes the {@link SynonymType synonym type} of the synonyms
34
 * that are homotypic to the accepted taxon to
35
 * {@link SynonymType#HOMOTYPIC_SYNONYM_OF() homotypic synonym of}.
36
 *
37
 * NOTE: It is still unclear where to put this kind of operations.
38
 * The base class, package and even the module may change in future.
39
 *
40
 * @author a.mueller
41
 * @date 22.04.2017
42
 *
43
 */
44
public class BasionymRelationCreator extends StrategyBase {
45

  
46
    private static final long serialVersionUID = -4711438819176248413L;
47
    @SuppressWarnings("unused")
48
    private static final Logger logger = Logger.getLogger(BasionymRelationCreator.class);
49

  
50
    private UUID uuid = UUID.fromString("e9e1d1f5-e398-4ba7-81a6-92875573d7cb");
51

  
52
    /**
53
     * {@inheritDoc}
54
     */
55
    @Override
56
    protected UUID getUuid() {
57
        return uuid;
58
    }
59

  
60
    public void invoke (Taxon taxon){
61
        Set<Synonym> synonyms = taxon.getSynonyms();
62

  
63
        //compare accepted against synonyms
64
        for (Synonym synonym: synonyms){
65
            TaxonNameBase<?, ?> basionym = compareHomotypic(taxon.getName(), synonym.getName());
66
            if (basionym != null){
67
                synonym.setType(SynonymType.HOMOTYPIC_SYNONYM_OF());
68
                adaptHomotypicGroup(basionym, taxon.getName(), synonym.getName());
69
            }
70
        }
71
        List<Synonym> synonymList = new ArrayList<>(synonyms);
72

  
73
        //compareEachSynonymAgainstEachOther;
74
        for (int i = 0; i < synonymList.size()-1; i++){
75
            for (int j = i + 1; j < synonymList.size(); j++){
76
                Synonym syn1 = synonymList.get(i);
77
                Synonym syn2 = synonymList.get(j);
78
                TaxonNameBase<?, ?> basionym = compareHomotypic(syn1.getName(), syn2.getName());
79
                if (basionym != null){
80
                    adaptHomotypicGroup(basionym, syn1.getName(), syn2.getName());
81
                    if (taxon.getName().getBasionyms().contains(basionym)){
82
                        syn1.setType(SynonymType.HOMOTYPIC_SYNONYM_OF());
83
                        syn2.setType(SynonymType.HOMOTYPIC_SYNONYM_OF());
84
                    }
85
                }
86
            }
87
        }
88
    }
89

  
90
    /**
91
     * @param basionym
92
     * @param name
93
     * @param name2
94
     */
95
    private void adaptHomotypicGroup(TaxonNameBase<?, ?> basionym,
96
            TaxonNameBase<?,?> name1, TaxonNameBase<?,?> name2) {
97
        if (basionym.equals(name1)){
98
            if (!name2.getBasionyms().contains(name1)){
99
                name2.addBasionym(name1);
100
            }
101
        }else if (basionym.equals(name2)){
102
            if (!name1.getBasionyms().contains(name2)){
103
                name1.addBasionym(name2);
104
            }
105
        }
106
    }
107

  
108
    /**
109
     * @param name
110
     * @param name2
111
     */
112
    private TaxonNameBase<?,?> compareHomotypic(TaxonNameBase<?,?> name1, TaxonNameBase<?,?> name2) {
113
        if (name1 == null || name2 == null){
114
            return null;
115
        }
116
        TaxonNameBase<?,?> basionymCandidate = checkAuthors(name1, name2);
117
        if (basionymCandidate == null){
118
            return null;
119
        }else{
120
            TaxonNameBase<?,?> newCombinationCandidate
121
                = basionymCandidate == name1? name2: name1;
122
            boolean isBasionym = compareNameParts(basionymCandidate, newCombinationCandidate);
123
            if (isBasionym){
124
                return basionymCandidate;
125
            }else{
126
                return null;
127
            }
128
        }
129
    }
130

  
131
    /**
132
     * @param basionymCandiate
133
     * @param newCombinationCandidate
134
     */
135
    private boolean compareNameParts(TaxonNameBase<?, ?> basionymCandidate,
136
            TaxonNameBase<?, ?> newCombinationCandidate) {
137
        if (basionymCandidate.isGenusOrSupraGeneric() || newCombinationCandidate.isGenusOrSupraGeneric()){
138
            return false;
139
        }else if (matchLastNamePart(basionymCandidate, newCombinationCandidate)){
140
            return true;
141
        }
142
        return false;
143
    }
144

  
145
    /**
146
     * @param name1
147
     * @param name2
148
     * @return
149
     */
150
    private TaxonNameBase<?,?> checkAuthors(TaxonNameBase<?, ?> name1, TaxonNameBase<?, ?> name2) {
151
        if (hasBasionymAuthorOf(name1, name2)){
152
            return name1;
153
        }else if (hasBasionymAuthorOf(name2, name1)){
154
            return name2;
155
        }else{
156
            return null;
157
        }
158
    }
159

  
160
    /**
161
     * @param name1
162
     * @param name2
163
     * @return
164
     */
165
    private boolean hasBasionymAuthorOf(TaxonNameBase<?,?> name1, TaxonNameBase<?,?> name2) {
166
        TeamOrPersonBase<?> basAuthor2 = name2.getBasionymAuthorship();
167
        TeamOrPersonBase<?> combinationAuthor = name1.getCombinationAuthorship();
168
        TeamOrPersonBase<?> basAuthor1 = name1.getBasionymAuthorship();
169
        if (basAuthor2 != null && basAuthor1 == null){
170
            if (matches(basAuthor2, combinationAuthor)){
171
                return true;
172
            }
173
        }
174
        return false;
175
    }
176

  
177
    /**
178
     * @param basAuthor
179
     * @param combinationAuthor
180
     * @return
181
     */
182
    private boolean matches(TeamOrPersonBase<?> basAuthor, TeamOrPersonBase<?> combinationAuthor) {
183
        //TODO better do with a CDM matcher that also compares other fields and
184
        //returns false if other fields are contradictory
185
        if (basAuthor == null || combinationAuthor == null){
186
            return false;
187
        }else if (basAuthor == combinationAuthor || basAuthor.equals(combinationAuthor)){
188
            return true;
189
        }else if (CdmUtils.nonEmptyEquals(basAuthor.getNomenclaturalTitle(), combinationAuthor.getNomenclaturalTitle())){
190
            return true;
191
        }else{
192
            return false;
193
        }
194
    }
195

  
196
    /**
197
     * @param basionymName
198
     * @param newCombination
199
     * @return
200
     */
201
    public static boolean matchLastNamePart(TaxonNameBase<?,?> name1, TaxonNameBase<?,?> name2) {
202
        String lastNamePart1 = name1.getLastNamePart();
203
        String lastNamePart2 = name2.getLastNamePart();
204
        if (lastNamePart1 != null && lastNamePart2 != null){
205
            lastNamePart1 = normalizeBasionymNamePart(lastNamePart1);
206
            lastNamePart2 = normalizeBasionymNamePart(lastNamePart2);
207
            return (lastNamePart1.equals(lastNamePart2));
208
        }else{
209
            return false;
210
        }
211
    }
212

  
213
    /**
214
     * @param lastNamePart1
215
     * @return
216
     */
217
    private static  String normalizeBasionymNamePart(String lastNamePart) {
218
        String namePart = lastNamePart.toLowerCase()
219
                .replaceAll("(um|us|a|is|e|os|on|or)$", "")
220
                .replaceAll("er$", "r")    //e.g. ruber <-> rubra
221
                .replaceAll("ese$", "s");  //e.g.  cayanensis <-> cayanenese
222
                //TODO tampensis / tampense
223
        return namePart;
224
    }
225

  
226
}
cdmlib-model/src/test/java/eu/etaxonomy/cdm/strategy/homotypicgroup/BasionymRelationCreatorTest.java
1
// $Id$
2
/**
3
* Copyright (C) 2017 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
package eu.etaxonomy.cdm.strategy.homotypicgroup;
11

  
12
import org.junit.Assert;
13
import org.junit.Before;
14
import org.junit.BeforeClass;
15
import org.junit.Test;
16

  
17
import eu.etaxonomy.cdm.model.agent.Person;
18
import eu.etaxonomy.cdm.model.common.DefaultTermInitializer;
19
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
20
import eu.etaxonomy.cdm.model.name.NameRelationshipType;
21
import eu.etaxonomy.cdm.model.name.Rank;
22
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
23
import eu.etaxonomy.cdm.model.name.TaxonNameFactory;
24
import eu.etaxonomy.cdm.model.taxon.Synonym;
25
import eu.etaxonomy.cdm.model.taxon.SynonymType;
26
import eu.etaxonomy.cdm.model.taxon.Taxon;
27

  
28
/**
29
 * @author a.mueller
30
 * @date 22.04.2017
31
 *
32
 */
33
public class BasionymRelationCreatorTest {
34

  
35
    /**
36
     *
37
     */
38
    private static final String SPECIUS = "specius";
39
    private Taxon taxon1;
40
    private Synonym synonym1;
41
    private Synonym synonym2;
42
    private Synonym synonym3;
43
    private TaxonNameBase<?,?> accName1;
44
    private TaxonNameBase<?,?> synName1;
45
    private TaxonNameBase<?,?> synName2;
46
    private TaxonNameBase<?,?> synName3;
47
    private Person person1;
48
    private Person person2;
49
    private Person person3;
50
    private BasionymRelationCreator guesser;
51

  
52

  
53
    /**
54
     * @throws java.lang.Exception
55
     */
56
    @BeforeClass
57
    public static void setUpBeforeClass() throws Exception {
58
        if (NameRelationshipType.BASIONYM() == null){
59
            new DefaultTermInitializer().initialize();
60
        }
61
    }
62

  
63
    /**
64
     * @throws java.lang.Exception
65
     */
66
    @Before
67
    public void setUp() throws Exception {
68
        guesser = new BasionymRelationCreator();
69

  
70
        person1 = Person.NewInstance();
71
        person2 = Person.NewInstance();
72
        person3 = Person.NewInstance();
73
        person1.setNomenclaturalTitle("Pers1");
74
        person2.setNomenclaturalTitle("Pers2");
75
        person3.setNomenclaturalTitle("Pers3");
76

  
77
        accName1 = TaxonNameFactory.NewBotanicalInstance(Rank.SPECIES());
78
        synName1 = TaxonNameFactory.NewBotanicalInstance(Rank.SPECIES());
79
        synName2 = TaxonNameFactory.NewBotanicalInstance(Rank.SPECIES());
80
        synName3 = TaxonNameFactory.NewBotanicalInstance(Rank.SPECIES());
81
        accName1.setGenusOrUninomial("Accepted");
82
        synName1.setGenusOrUninomial("Genus1");
83
        synName2.setGenusOrUninomial("Genus2");
84
        synName3.setGenusOrUninomial("Genus3");
85
        accName1.setSpecificEpithet(SPECIUS);
86
        synName1.setSpecificEpithet(SPECIUS);
87
        synName2.setSpecificEpithet(SPECIUS);
88
        synName3.setSpecificEpithet(SPECIUS);
89

  
90
        taxon1 = Taxon.NewInstance(accName1, null);
91
        synonym1 = Synonym.NewInstance(synName1, null);
92
        synonym2 = Synonym.NewInstance(synName2, null);
93
        synonym3 = Synonym.NewInstance(synName3, null);
94

  
95
        taxon1.addSynonym(synonym1, SynonymType.SYNONYM_OF());
96
        taxon1.addSynonym(synonym2, SynonymType.SYNONYM_OF());
97
        taxon1.addSynonym(synonym3, SynonymType.SYNONYM_OF());
98
    }
99

  
100
    @Test
101
    public void testMatchingSimple() {
102
        accName1.setCombinationAuthorship(person1);
103
        synName1.setBasionymAuthorship(person1);
104
        synName2.setBasionymAuthorship(person1);
105
        synName2.setInfraSpecificEpithet(synName2.getSpecificEpithet());
106
        synName2.setSpecificEpithet("xyz");
107
        synName2.setRank(Rank.VARIETY());
108
        synName3.setBasionymAuthorship(person3);
109
        HomotypicalGroup accNameGroup = accName1.getHomotypicalGroup();
110
        guesser.invoke(taxon1);
111
        Assert.assertEquals("Accepted and synonym1 should have same homotypic group", accName1.getHomotypicalGroup(), synName1.getHomotypicalGroup());
112
        Assert.assertEquals("Accepted and synonym2 should have same homotypic group", accName1.getHomotypicalGroup(), synName2.getHomotypicalGroup());
113
        Assert.assertEquals("Homotypical group shoul be taken from basionym", accNameGroup, accName1.getHomotypicalGroup());
114
        Assert.assertNotEquals("Accepted and synonym3 should not have same homotypic group due to different author", accName1.getHomotypicalGroup(), synName3.getHomotypicalGroup());
115
        Assert.assertEquals(SynonymType.HOMOTYPIC_SYNONYM_OF(), synonym1.getType());
116
        Assert.assertEquals(SynonymType.HOMOTYPIC_SYNONYM_OF(), synonym2.getType());
117
        Assert.assertEquals(SynonymType.SYNONYM_OF(), synonym3.getType());
118
    }
119

  
120
    @Test
121
    public void testMatchingSynonym() {
122
        accName1.setBasionymAuthorship(person1);
123
        synName1.setCombinationAuthorship(person1);
124
        synName2.setBasionymAuthorship(person1);
125
        synName2.setInfraSpecificEpithet(synName2.getSpecificEpithet());
126
        synName2.setSpecificEpithet("xyz");
127
        synName2.setRank(Rank.VARIETY());
128
        synName3.setBasionymAuthorship(person3);
129
        HomotypicalGroup accNameGroup = accName1.getHomotypicalGroup();
130
        guesser.invoke(taxon1);
131
        Assert.assertEquals("Accepted and synonym1 should have same homotypic group", accName1.getHomotypicalGroup(), synName1.getHomotypicalGroup());
132
        Assert.assertEquals("Synonym1 and synonym2 should have same homotypic group", accName1.getHomotypicalGroup(), synName2.getHomotypicalGroup());
133
        Assert.assertEquals("Accepted and synonym2 should have same homotypic group", accName1.getHomotypicalGroup(), synName2.getHomotypicalGroup());
134
        Assert.assertEquals("Homotypical group shoul be taken from basionym", accNameGroup, accName1.getHomotypicalGroup());
135
        Assert.assertNotEquals("Accepted and synonym3 should not have same homotypic group due to different author", accName1.getHomotypicalGroup(), synName3.getHomotypicalGroup());
136
        Assert.assertEquals(SynonymType.HOMOTYPIC_SYNONYM_OF(), synonym1.getType());
137
        Assert.assertEquals(SynonymType.HOMOTYPIC_SYNONYM_OF(), synonym2.getType());
138
        Assert.assertEquals(SynonymType.SYNONYM_OF(), synonym3.getType());
139
    }
140

  
141
    @Test
142
    public void testNonMatchingSimple() {
143
        accName1.setCombinationAuthorship(person1);
144
        synName1.setBasionymAuthorship(person1);
145
        synName1.setSpecificEpithet("spefides");
146
        synName2.setBasionymAuthorship(person2);
147
        HomotypicalGroup accNameGroup = accName1.getHomotypicalGroup();
148
        guesser.invoke(taxon1);
149
        Assert.assertEquals("Homotypical group shoul be taken from basionym", accNameGroup, accName1.getHomotypicalGroup());
150
        Assert.assertNotEquals("Different last epithets should not match", accName1.getHomotypicalGroup(), synName1.getHomotypicalGroup());
151
        Assert.assertNotEquals("Different authors should not match", accName1.getHomotypicalGroup(), synName2.getHomotypicalGroup());
152
        Assert.assertNotEquals("Missing basionym author should not match", accName1.getHomotypicalGroup(), synName3.getHomotypicalGroup());
153
        Assert.assertEquals(SynonymType.SYNONYM_OF(), synonym1.getType());
154
        Assert.assertEquals(SynonymType.SYNONYM_OF(), synonym2.getType());
155
        Assert.assertEquals(SynonymType.SYNONYM_OF(), synonym3.getType());
156
    }
157

  
158
    @Test
159
    public void testMatchingNormalization() {
160
        accName1.setCombinationAuthorship(person1);
161
        synName1.setBasionymAuthorship(person1);
162
        synName1.setSpecificEpithet("specia");
163
        synName2.setBasionymAuthorship(person1);
164
        synName2.setInfraSpecificEpithet("specios");
165
        synName2.setSpecificEpithet("xyz");
166
        synName2.setRank(Rank.VARIETY());
167
        synName3.setBasionymAuthorship(person1);
168
        synName1.setSpecificEpithet("specium");
169

  
170
        guesser.invoke(taxon1);
171
        Assert.assertEquals("Accepted and synonym1 should have same homotypic group", accName1.getHomotypicalGroup(), synName1.getHomotypicalGroup());
172
        Assert.assertEquals("Accepted and synonym2 should have same homotypic group", accName1.getHomotypicalGroup(), synName2.getHomotypicalGroup());
173
        Assert.assertEquals("Accepted and synonym3 should have same homotypic group", accName1.getHomotypicalGroup(), synName3.getHomotypicalGroup());
174
        Assert.assertEquals(SynonymType.HOMOTYPIC_SYNONYM_OF(), synonym1.getType());
175
        Assert.assertEquals(SynonymType.HOMOTYPIC_SYNONYM_OF(), synonym2.getType());
176
        Assert.assertEquals(SynonymType.HOMOTYPIC_SYNONYM_OF(), synonym3.getType());
177
    }
178

  
179
    @Test
180
    public void testMatchingNomTitle() {
181
        accName1.setCombinationAuthorship(person1);
182
        synName1.setBasionymAuthorship(person2);
183
        guesser.invoke(taxon1);
184
        Assert.assertNotEquals("Accepted and synonym1 should NOT have same homotypic group", accName1.getHomotypicalGroup(), synName1.getHomotypicalGroup());
185
        Assert.assertEquals(SynonymType.SYNONYM_OF(), synonym1.getType());
186

  
187
        person2.setNomenclaturalTitle(person1.getNomenclaturalTitle());
188
        guesser.invoke(taxon1);
189
        Assert.assertEquals("Accepted and synonym1 should have same homotypic group", accName1.getHomotypicalGroup(), synName1.getHomotypicalGroup());
190
        Assert.assertEquals(SynonymType.HOMOTYPIC_SYNONYM_OF(), synonym1.getType());
191
    }
192

  
193
}

Also available in: Unified diff

Add picture from clipboard (Maximum size: 40 MB)