minor
[cdmlib.git] / 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.TaxonName;
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 TaxonName 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 * @since 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 TaxonName 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 TaxonName 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(TaxonName basionym,
96 TaxonName name1, TaxonName 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 TaxonName compareHomotypic(TaxonName name1, TaxonName name2) {
113 if (name1 == null || name2 == null){
114 return null;
115 }
116 TaxonName basionymCandidate = checkAuthors(name1, name2);
117 if (basionymCandidate == null){
118 return null;
119 }else{
120 TaxonName 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(TaxonName basionymCandidate,
136 TaxonName 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 TaxonName checkAuthors(TaxonName name1, TaxonName 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(TaxonName name1, TaxonName 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(TaxonName name1, TaxonName name2) {
202 String familyNamePart1 = name1.getLastNamePart();
203 String familyNamePart2 = name2.getLastNamePart();
204 if (familyNamePart1 != null && familyNamePart2 != null){
205 familyNamePart1 = normalizeBasionymNamePart(familyNamePart1);
206 familyNamePart2 = normalizeBasionymNamePart(familyNamePart2);
207 return (familyNamePart1.equals(familyNamePart2));
208 }else{
209 return false;
210 }
211 }
212
213 /**
214 * @param familyNamePart
215 * @return
216 */
217 private static String normalizeBasionymNamePart(String familyNamePart) {
218 String namePart = familyNamePart.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 }