duplicate arbitration now kicks in when name composites focus is lost
[taxeditor.git] / taxeditor-editor / src / main / java / eu / etaxonomy / taxeditor / editor / DuplicateArbitrator.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.taxeditor.editor;
11
12 import java.util.ArrayList;
13 import java.util.List;
14 import java.util.Set;
15
16 import org.apache.log4j.Logger;
17
18 import eu.etaxonomy.cdm.api.service.ICommonService;
19 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
20 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
21 import eu.etaxonomy.cdm.model.name.NonViralName;
22 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
23 import eu.etaxonomy.cdm.model.reference.ReferenceBase;
24 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
25 import eu.etaxonomy.cdm.strategy.match.DefaultMatchStrategy;
26 import eu.etaxonomy.cdm.strategy.match.IMatchStrategy;
27 import eu.etaxonomy.cdm.strategy.match.MatchException;
28 import eu.etaxonomy.cdm.strategy.match.MatchMode;
29 import eu.etaxonomy.taxeditor.editor.name.NameComposite;
30 import eu.etaxonomy.taxeditor.editor.name.TaxonNameEditor;
31 import eu.etaxonomy.taxeditor.store.CdmStore;
32
33 /**
34 * This class is supposed to handle possible duplications of cdm entities
35 * for a specific editor
36 *
37 * TODO break dependency to editor
38 *
39 * @author n.hoffmann
40 * @created 03.07.2009
41 * @version 1.0
42 *
43 */
44 public class DuplicateArbitrator {
45 private static final Logger logger = Logger.getLogger(DuplicateArbitrator.class);
46
47 /**
48 * The editor this arbitrator should manage possible duplicates for
49 */
50 private MultiPageTaxonEditor editor;
51 private TaxonNameEditor nameEditor;
52 private Set<NameComposite<TaxonBase>> dirtyNameComposites;
53
54 private List<ReferenceBase> duplicateReferences;
55
56 private List<TaxonNameBase> duplicateNames;
57
58 private List<TeamOrPersonBase> duplicateCombinationAuthorTeams;
59 private List<TeamOrPersonBase> duplicateExCombinationAuthorTeams;
60 private List<TeamOrPersonBase> duplicateBasionymAuthorTeams;
61 private List<TeamOrPersonBase> duplicateExBasionymAuthorTeams;
62
63 private ICommonService commonService;
64
65
66 /**
67 * @param adaptableObject
68 */
69 public DuplicateArbitrator(MultiPageTaxonEditor editor) {
70 this.editor = editor;
71
72 commonService = CdmStore.getCommonService();
73
74 nameEditor = (TaxonNameEditor) editor.getPage(Page.NAME);
75
76 duplicateReferences = new ArrayList<ReferenceBase>();
77 duplicateNames = new ArrayList<TaxonNameBase>();
78 duplicateCombinationAuthorTeams = new ArrayList<TeamOrPersonBase>();
79 duplicateExCombinationAuthorTeams = new ArrayList<TeamOrPersonBase>();
80 duplicateBasionymAuthorTeams = new ArrayList<TeamOrPersonBase>();
81 duplicateExBasionymAuthorTeams = new ArrayList<TeamOrPersonBase>();
82 }
83
84 /**
85 * Provide strategies on how to handle possible duplications
86 */
87 public void arbitrate() {
88 // iterate over all names that were edited
89 for(NameComposite composite : nameEditor.getDirtyNames()){
90 logger.warn("Found " + composite + " with possible duplicates");
91
92 arbitrate(composite);
93 }
94 }
95
96 public void arbitrate(NameComposite composite){
97 NonViralName nonViralName = (NonViralName) composite.getParsedName();
98
99 duplicateNames = findMatchingLatinNames(nonViralName);
100 duplicateCombinationAuthorTeams = findMatchingAuthors((TeamOrPersonBase) nonViralName.getCombinationAuthorTeam());
101 duplicateExCombinationAuthorTeams = findMatchingAuthors((TeamOrPersonBase) nonViralName.getExCombinationAuthorTeam());
102 duplicateBasionymAuthorTeams = findMatchingAuthors((TeamOrPersonBase) nonViralName.getBasionymAuthorTeam());
103 duplicateExBasionymAuthorTeams = findMatchingAuthors((TeamOrPersonBase) nonViralName.getExBasionymAuthorTeam());
104 duplicateReferences = findMatchingNomenclaturalReference(nonViralName.getNomenclaturalReference());
105
106 saveNameElements(composite);
107
108 emptyDuplicateSets();
109 }
110
111 /**
112 *
113 */
114 private void emptyDuplicateSets() {
115 duplicateReferences.clear();
116 duplicateCombinationAuthorTeams.clear();
117 duplicateExCombinationAuthorTeams.clear();
118 duplicateBasionymAuthorTeams.clear();
119 duplicateExBasionymAuthorTeams.clear();
120 duplicateNames.clear();
121 }
122
123 /**
124 * Provides strategies to solve found duplicates
125 * This can either happen automatically or through user input
126 */
127 private void saveNameElements(NameComposite composite) {
128
129 resolveDuplicateNames(composite);
130
131 resolveAllDuplicateAuthors(composite);
132
133 resolveDuplicateReferences(composite);
134
135 // FIXME this is a workaround, because title cache does not get generated correctly in library
136 // remove once #964 is closed
137 composite.getTaxon().setTitleCache((composite.getTaxon().generateTitle()));
138 }
139
140 /**
141 * @param composite
142 */
143 private void resolveDuplicateNames(NameComposite composite) {
144
145 /* When creating a new taxon editor, a taxon with an empty name is created.
146 * We have to delete this empty name explicitly from the session, otherwise it
147 * will be persisted into the database
148 */
149 if(composite.getName().getFullTitleCache().length() == 0){
150 editor.getConversationHolder().delete(composite.getName());
151 }
152
153 if(duplicateNames.size() == 0){
154 // No matches were found for the name part. We can safely
155 // replace the taxons name with the parsed name
156
157 composite.setName(composite.getParsedName());
158 }else if (duplicateNames.size() == 1){
159 composite.setName(duplicateNames.iterator().next());
160 }else{
161 // FIXME TODO resolve multiple duplications. Use first match for a start
162 composite.setName(duplicateNames.iterator().next());
163 }
164
165 // debug
166 for(TaxonNameBase name : duplicateNames){
167 logger.info(name.getFullTitleCache());
168 }
169
170 }
171
172 /**
173 * @param composite
174 */
175 private void resolveDuplicateReferences(NameComposite composite) {
176 if(duplicateReferences.size() == 0){
177 // nomatches found for the reference we replace the reference with the parsed one
178 composite.getName().setNomenclaturalReference(composite.getParsedName().getNomenclaturalReference());
179 }else if(duplicateReferences.size() == 1){
180 // exactly one match. We assume that the user wants this reference
181 composite.getName().setNomenclaturalReference(duplicateReferences.iterator().next());
182 }else{
183 // FIXME TODO resolve multiple duplications. Use first match for a start
184 composite.getName().setNomenclaturalReference(duplicateReferences.iterator().next());
185 }
186
187 // debug
188 for(ReferenceBase reference : duplicateReferences){
189 logger.info(reference.getTitleCache());
190 }
191 }
192
193
194
195 /**
196 * @param composite
197 */
198 private void resolveAllDuplicateAuthors(NameComposite composite) {
199 NonViralName name = HibernateProxyHelper.deproxy(composite.getName(), NonViralName.class);
200 NonViralName parsedName = HibernateProxyHelper.deproxy(composite.getParsedName(), NonViralName.class);
201
202
203 if(duplicateCombinationAuthorTeams.size() == 0){
204 name.setCombinationAuthorTeam(parsedName.getCombinationAuthorTeam());
205 }else if(duplicateCombinationAuthorTeams.size() == 1){
206 name.setCombinationAuthorTeam(duplicateCombinationAuthorTeams.iterator().next());
207 }else{
208 // FIXME TODO resolve multiple duplications. Use first match for a start
209 name.setCombinationAuthorTeam(duplicateCombinationAuthorTeams.iterator().next());
210 }
211
212 if(duplicateExCombinationAuthorTeams.size() == 0){
213 name.setExCombinationAuthorTeam(parsedName.getExCombinationAuthorTeam());
214 }else if(duplicateExCombinationAuthorTeams.size() == 1){
215 name.setExCombinationAuthorTeam(duplicateExCombinationAuthorTeams.iterator().next());
216 }else{
217 // FIXME TODO resolve multiple duplications. Use first match for a start
218 name.setExCombinationAuthorTeam(duplicateExCombinationAuthorTeams.iterator().next());
219 }
220
221 if(duplicateBasionymAuthorTeams.size() == 0){
222 name.setBasionymAuthorTeam(parsedName.getBasionymAuthorTeam());
223 }else if(duplicateBasionymAuthorTeams.size() == 1){
224 name.setBasionymAuthorTeam(duplicateBasionymAuthorTeams.iterator().next());
225 }else{
226 // FIXME TODO resolve multiple duplications. Use first match for a start
227 name.setBasionymAuthorTeam(duplicateBasionymAuthorTeams.iterator().next());
228 }
229
230 if(duplicateExBasionymAuthorTeams.size() == 0){
231 name.setExBasionymAuthorTeam(parsedName.getExBasionymAuthorTeam());
232 }else if(duplicateExBasionymAuthorTeams.size() == 1){
233 name.setExBasionymAuthorTeam(duplicateExBasionymAuthorTeams.iterator().next());
234 }else{
235 // FIXME TODO resolve multiple duplications. Use first match for a start
236 name.setExBasionymAuthorTeam(duplicateExBasionymAuthorTeams.iterator().next());
237 }
238
239
240 // debug
241 for(TeamOrPersonBase authorTeam : duplicateCombinationAuthorTeams){
242 logger.info(authorTeam.getTitleCache());
243 }
244 // debug
245 for(TeamOrPersonBase authorTeam : duplicateExCombinationAuthorTeams){
246 logger.info(authorTeam.getTitleCache());
247 }
248 // debug
249 for(TeamOrPersonBase authorTeam : duplicateBasionymAuthorTeams){
250 logger.info(authorTeam.getTitleCache());
251 }
252 // debug
253 for(TeamOrPersonBase authorTeam : duplicateExBasionymAuthorTeams){
254 logger.info(authorTeam.getTitleCache());
255 }
256 }
257
258
259 /**
260 * @param name
261 */
262 private List<ReferenceBase> findMatchingNomenclaturalReference(ReferenceBase referenceBase) {
263 if(referenceBase == null) return new ArrayList<ReferenceBase>();
264 try{
265 return commonService.findMatching(referenceBase, null);
266 }catch (MatchException e) {
267 logger.error("Error finding matching references", e);
268 }
269 return null;
270 }
271
272 /**
273 * @param name
274 */
275 private List<TeamOrPersonBase> findMatchingAuthors(TeamOrPersonBase authorTeam) {
276
277 if(authorTeam == null){
278 return new ArrayList<TeamOrPersonBase>();
279 }
280
281 try{
282 return commonService.findMatching(authorTeam, null);
283 }catch (MatchException e) {
284 logger.error("Error finding matching authors", e);
285 }
286 return null;
287 }
288
289 /**
290 * @param name
291 */
292 private List<TaxonNameBase> findMatchingLatinNames(TaxonNameBase taxonNameBase) {
293
294 try {
295 IMatchStrategy strategy = DefaultMatchStrategy.NewInstance(NonViralName.class);
296 strategy.setMatchMode("nomenclaturalReference", MatchMode.IGNORE);
297 strategy.setMatchMode("combinationAuthorTeam", MatchMode.IGNORE);
298 strategy.setMatchMode("exCombinationAuthorTeam", MatchMode.IGNORE);
299 strategy.setMatchMode("basionymAuthorTeam", MatchMode.IGNORE);
300 strategy.setMatchMode("exBasionymAuthorTeam", MatchMode.IGNORE);
301
302 return commonService.findMatching(taxonNameBase, strategy);
303
304 } catch (MatchException e) {
305 logger.error("Error finding matching names", e);
306 }
307 return null;
308 }
309 }