ref #9078 fix caching for deduplicated name in ParseHandler
[taxeditor.git] / eu.etaxonomy.taxeditor.store / src / main / java / eu / etaxonomy / taxeditor / parser / ParseHandler.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 package eu.etaxonomy.taxeditor.parser;
10
11 import java.util.ArrayList;
12 import java.util.List;
13
14 import org.apache.log4j.Logger;
15
16 import eu.etaxonomy.cdm.api.service.INameService;
17 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
18 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
19 import eu.etaxonomy.cdm.model.name.INonViralName;
20 import eu.etaxonomy.cdm.model.name.ITaxonNameBase;
21 import eu.etaxonomy.cdm.model.name.Rank;
22 import eu.etaxonomy.cdm.model.name.TaxonName;
23 import eu.etaxonomy.cdm.model.name.TaxonNameFactory;
24 import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
25 import eu.etaxonomy.cdm.model.reference.Reference;
26 import eu.etaxonomy.cdm.strategy.match.MatchException;
27 import eu.etaxonomy.cdm.strategy.match.MatchStrategyConfigurator.MatchStrategy;
28 import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
29 import eu.etaxonomy.taxeditor.model.MessagingUtils;
30 import eu.etaxonomy.taxeditor.preference.PreferencesUtil;
31 import eu.etaxonomy.taxeditor.store.CdmStore;
32
33 /**
34 * <p>ParseHandler class.</p>
35 *
36 * @author n.hoffmann
37 * @author a.mueller
38 */
39 public class ParseHandler{
40
41 public static final Logger logger = Logger.getLogger(ParseHandler.class);
42
43
44 private static NonViralNameParserImpl nonViralNameParser = NonViralNameParserImpl.NewInstance();
45
46 /**
47 * The name that should get parsed
48 */
49 private TaxonName name;
50
51 /**
52 * Creates a new instance
53 */
54 private ParseHandler(TaxonName name){
55 if(name == null){
56 this.name = createEmptyName();
57 }else{
58 this.name = HibernateProxyHelper.deproxy(name);
59 }
60 }
61
62 /**
63 * Factory method to create a new instance of the this class
64 *
65 * @param textWidget The text widget where the NonViralName may be entered into
66 * @param name An initial NonViralName or null when creating a new name
67 * @return An instance of this class
68 */
69 public static ParseHandler NewInstance(TaxonName name){
70 return new ParseHandler(name);
71 }
72
73 /**
74 * Parses a given string and returns a <code>TaxonName</code> instance with the
75 * results of the parsing.
76 *
77 * This method should be used to quickly create a new name from a string.
78 * Wherever the string will be parsed again in subsequent editing, an instance
79 * of <code>ParseHandler</code> should be attached to the text widget.
80 *
81 * @param unparsedNameString a {@link java.lang.String} object.
82 * @return a {@link eu.etaxonomy.cdm.model.name.TaxonNameBase} object.
83 */
84 public static INonViralName parseReferencedName(String unparsedNameString, Rank rank){
85 INonViralName name = nonViralNameParser.parseReferencedName(unparsedNameString,
86 PreferencesUtil.getPreferredNomenclaturalCode(), rank);
87 return name;
88 }
89
90 public static TaxonName parseName(String unparsedNameString, Rank rank){
91 INonViralName name = nonViralNameParser.parseFullName(unparsedNameString,
92 PreferencesUtil.getPreferredNomenclaturalCode(), rank);
93 return (TaxonName)name;
94 }
95
96 /**
97 * Creates an empty <code>TaxonName</code> instance with the nomenclatural code
98 * currently set in preferences.
99 *
100 * @return the newly created {@link TaxonName}.
101 */
102 public static TaxonName createEmptyName(){
103 return TaxonNameFactory.NewNameInstance(PreferencesUtil.getPreferredNomenclaturalCode(), null);
104 }
105
106 /**
107 * Parses the string that was entered into the text widget and returns a
108 * NonViralName object that resulted from the parsing process.
109 *
110 * @return The parsed NonViralName object
111 */
112 public INonViralName parse(String unparsedNameString){
113 nonViralNameParser.parseReferencedName(name, unparsedNameString,
114 name.getRank(), true);
115 return name;
116 }
117
118 public INonViralName parseAndResolveDuplicates(String unparsedNameString){
119 // Instant time = java.time.Instant.now();
120 // logger.warn("Start resolve duplicate");
121 INonViralName result = parseAndResolveDuplicatesNew(unparsedNameString);
122 // INonViralName result = parseAndResolveDuplicatesOld(unparsedNameString);
123 // logger.warn("End resolve duplicate: "+ time.until(Instant.now(), ChronoUnit.MILLIS));
124 return result;
125 }
126
127
128 /**
129 * Parses the string that was entered into the text widget and returns a
130 * TaxonName object that resulted from the parsing process.
131 *
132 * The atomized fields (scientific name, author and reference) will be matched
133 * against the database to find possible duplicates. If duplicates were found
134 * the respective parts of the NonViralName will be replaced with the found
135 * objects.
136 *
137 * @return The parsed NonViralName object
138 */
139 public TaxonName parseAndResolveDuplicatesNew(String unparsedNameString){
140
141 CdmStore.getService(INameService.class).parseName(name, unparsedNameString, null, true, true);
142 return name;
143 }
144
145 //********************************** OLD can be removed once parseAndResolveDuplicatesNew is stable ************************************/
146 private class MatchMatrix {
147 List<TaxonName> duplicateNames = new ArrayList<>();
148
149 List<INomenclaturalReference> duplicateReferences = new ArrayList<>();
150 List<INomenclaturalReference> duplicateInReferences = new ArrayList<>();
151
152 List<TeamOrPersonBase> duplicateCombinationAuthorships = new ArrayList<>();
153 List<TeamOrPersonBase> duplicateExCombinationAuthorships = new ArrayList<>();
154 List<TeamOrPersonBase> duplicateBasionymAuthorships = new ArrayList<>();
155 List<TeamOrPersonBase> duplicateExBasionymAuthorships = new ArrayList<>();
156 }
157
158 /**
159 * Creates an empty <code>TaxonNameBase</code> instance with the nomenclatural code
160 * currently set in preferences.
161 *
162 * @return a {@link eu.etaxonomy.cdm.model.name.TaxonNameBase} object.
163 */
164 public static TaxonName createEmptyNameOld(){
165 return (TaxonName)nonViralNameParser.getNonViralNameInstance("", PreferencesUtil.getPreferredNomenclaturalCode());
166 }
167
168 /**
169 * Parses the string that was entered into the text widget and returns a
170 * NonViralName object that resulted from the parsing process.
171 *
172 * The atomized fields (scientific name, author and reference) will be matched
173 * against the database to find possible duplicates. If duplicates were found
174 * the respective parts of the NonViralName will be replaced with the found
175 * objects.
176 *
177 * @return The parsed NonViralName object
178 */
179 public INonViralName parseAndResolveDuplicatesOld(String unparsedNameString){
180
181 INonViralName parsedName = parse(unparsedNameString);
182
183 MatchMatrix matchMatrix = findMatches(parsedName);
184
185 resolveDuplicates(parsedName, matchMatrix);
186
187 return parsedName;
188 }
189
190
191
192
193 /**
194 * @param name The name to resolve duplicates for.
195 */
196 private void resolveDuplicates(INonViralName name, MatchMatrix matchMatrix) {
197 resolveDuplicateNames(name, matchMatrix);
198
199 resolveAllDuplicateAuthors(name, matchMatrix);
200
201 resolveDuplicateReferences(name, matchMatrix);
202
203 // if(matchMatrix.duplicateInReferences != null) {
204 // resolveDuplicateInReferences(name, matchMatrix);
205 // }
206 }
207
208
209 /**
210 * @param name The name to resolve duplicates for.
211 */
212 private void resolveDuplicateNames(INonViralName name, MatchMatrix matchMatrix) {
213
214 if (matchMatrix.duplicateNames.size() == 1){
215 name = matchMatrix.duplicateNames.iterator().next();
216 }else if(matchMatrix.duplicateNames.size() > 1){
217 // FIXME TODO resolve multiple duplications. Use first match for a start
218 name = matchMatrix.duplicateNames.iterator().next();
219 }
220 }
221
222 /**
223 * @param name The name to resolve duplicates for.
224 */
225 private void resolveDuplicateReferences(INonViralName name, MatchMatrix matchMatrix) {
226 if(matchMatrix.duplicateReferences.size() == 1){
227 // exactly one match. We assume that the user wants this reference
228 INomenclaturalReference duplicate = matchMatrix.duplicateReferences.iterator().next();
229 name.setNomenclaturalReference(duplicate);
230 }else if(matchMatrix.duplicateReferences.size() > 1){
231 // FIXME TODO resolve multiple duplications. Use first match for a start
232 INomenclaturalReference duplicate = matchMatrix.duplicateReferences.iterator().next();
233 name.setNomenclaturalReference(duplicate);
234 }
235 //if reference is new but the in reference is already in db
236 if (matchMatrix.duplicateReferences.size() == 0 && (name.getNomenclaturalReference() != null && name.getNomenclaturalReference().getInReference() != null) && matchMatrix.duplicateInReferences.size() > 0){
237 resolveDuplicateInReferences(name, matchMatrix);
238 }
239 }
240
241 /**
242 * @param name The name to resolve duplicates for.
243 */
244 private void resolveDuplicateInReferences(INonViralName name, MatchMatrix matchMatrix) {
245 Reference reference = HibernateProxyHelper.deproxy(name.getNomenclaturalReference());
246
247 if(matchMatrix.duplicateInReferences.size() > 0){
248 Reference inReference = (Reference) matchMatrix.duplicateInReferences.iterator().next();
249 reference.setInReference(inReference);
250 MessagingUtils.warn(this.getClass(), reference.generateTitle());
251 // FIXME TODO resolve multiple duplications. We use first match for a start
252 MessagingUtils.warn(this.getClass(), reference.getTitleCache());
253 }
254 }
255
256
257 /**
258 * @param name The name to resolve duplicates for.
259 */
260 private void resolveAllDuplicateAuthors(INonViralName name, MatchMatrix matchMatrix) {
261
262 if(matchMatrix.duplicateCombinationAuthorships.size() > 0){
263 name.setCombinationAuthorship(matchMatrix.duplicateCombinationAuthorships.iterator().next());
264 Reference reference = name.getNomenclaturalReference();
265 if(reference != null){
266 reference.setAuthorship(matchMatrix.duplicateCombinationAuthorships.iterator().next());
267 }
268 // FIXME TODO resolve multiple duplications. We use first match for a start.
269 }
270
271 if(matchMatrix.duplicateExCombinationAuthorships.size() > 0){
272 name.setExCombinationAuthorship(matchMatrix.duplicateExCombinationAuthorships.iterator().next());
273 // FIXME TODO resolve multiple duplications. We use first match for a start.
274 }
275
276 if(matchMatrix.duplicateBasionymAuthorships.size() > 0){
277 name.setBasionymAuthorship(matchMatrix.duplicateBasionymAuthorships.iterator().next());
278 // FIXME TODO resolve multiple duplications. We use first match for a start.
279 }
280
281 if(matchMatrix.duplicateExBasionymAuthorships.size() > 0){
282 name.setExBasionymAuthorship(matchMatrix.duplicateExBasionymAuthorships.iterator().next());
283 // FIXME TODO resolve multiple duplications. We use first match for a start.
284 }
285 }
286
287 /**
288 * Splits a NonViralName into its parts and calls methods to find matches for these
289 * parts in the database.
290 *
291 * @param name The NonViralName to find matches for.
292 */
293 private MatchMatrix findMatches(INonViralName name){
294
295 MatchMatrix matchMatrix = new MatchMatrix();
296
297 matchMatrix.duplicateNames = findMatchingLatinNames(name);
298
299 matchMatrix.duplicateCombinationAuthorships = findMatchingAuthors(name.getCombinationAuthorship());
300 matchMatrix.duplicateExCombinationAuthorships = findMatchingAuthors(name.getExCombinationAuthorship());
301 matchMatrix.duplicateBasionymAuthorships = findMatchingAuthors(name.getBasionymAuthorship());
302 matchMatrix.duplicateExBasionymAuthorships = findMatchingAuthors(name.getExBasionymAuthorship());
303
304 INomenclaturalReference nomenclaturalReference = name.getNomenclaturalReference();
305
306 // check if the reference has an inreference and also check if the inReference already exists
307 if(nomenclaturalReference != null){
308 Reference inReference = ((Reference)nomenclaturalReference).getInReference();
309 if(inReference != null){
310 matchMatrix.duplicateInReferences = findMatchingNomenclaturalReference(inReference);
311 }
312 }
313
314 matchMatrix.duplicateReferences = findMatchingNomenclaturalReference(nomenclaturalReference);
315
316 return matchMatrix;
317 }
318
319 /**
320 * @param nomenclaturalReference The NomenclaturalReference to find matches for.
321 * @return A <code>List</code> of possibly matching NomenclaturalReference's.
322 */
323 private List<INomenclaturalReference> findMatchingNomenclaturalReference(INomenclaturalReference nomenclaturalReference) {
324 if(nomenclaturalReference == null) {
325 return new ArrayList<INomenclaturalReference>();
326 }
327 try{
328
329 return CdmStore.getCommonService().findMatching(nomenclaturalReference, MatchStrategy.Reference);
330
331 }catch (MatchException e) {
332 MessagingUtils.error(this.getClass(), "Error finding matching references", e);
333 }
334 return null;
335 }
336
337 /**
338 * @param authorTeam The TeamOrPersonBase to find matches for.
339 * @return A <code>List</code> of possibly matching TeamOrPersonBase's.
340 */
341 private List<TeamOrPersonBase> findMatchingAuthors(TeamOrPersonBase authorTeam) {
342 if(authorTeam == null){
343 return new ArrayList<>();
344 }
345 try{
346 return CdmStore.getCommonService().findMatching(authorTeam, MatchStrategy.TeamOrPerson);
347
348 }catch (MatchException e) {
349 MessagingUtils.error(this.getClass(), "Error finding matching authors", e);
350 }
351 return null;
352 }
353
354 /**
355 * @param taxonName The TaxonNameBase to find matches for.
356 * @return A <code>List</code> of possibly matching TaxonNameBase's.
357 */
358 private List<TaxonName> findMatchingLatinNames(ITaxonNameBase taxonName) {
359 try {
360 return CdmStore.getCommonService().findMatching(TaxonName.castAndDeproxy(taxonName), MatchStrategy.NonViralName);
361 } catch (MatchException e) {
362 MessagingUtils.error(this.getClass(), "Error finding matching names", e);
363 }
364 return null;
365 }
366
367 }