Merge branch 'release/5.18.0'
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / strategy / cache / name / TaxonNameDefaultCacheStrategy.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.cdm.strategy.cache.name;
10
11 import java.util.ArrayList;
12 import java.util.Arrays;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Set;
16 import java.util.UUID;
17
18 import org.apache.log4j.Logger;
19
20 import eu.etaxonomy.cdm.common.CdmUtils;
21 import eu.etaxonomy.cdm.common.UTF8;
22 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
23 import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
24 import eu.etaxonomy.cdm.model.common.Language;
25 import eu.etaxonomy.cdm.model.name.HybridRelationship;
26 import eu.etaxonomy.cdm.model.name.INonViralName;
27 import eu.etaxonomy.cdm.model.name.NomenclaturalStatus;
28 import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
29 import eu.etaxonomy.cdm.model.name.Rank;
30 import eu.etaxonomy.cdm.model.name.TaxonName;
31 import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
32 import eu.etaxonomy.cdm.model.reference.Reference;
33 import eu.etaxonomy.cdm.model.term.Representation;
34 import eu.etaxonomy.cdm.ref.TypedEntityReference;
35 import eu.etaxonomy.cdm.strategy.cache.TagEnum;
36 import eu.etaxonomy.cdm.strategy.cache.TaggedText;
37 import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
38 import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImplRegExBase;
39
40
41 /**
42 * This class is a default implementation for the INonViralNameCacheStrategy<T extends NonViralName>
43 * interface.<BR>
44 * The method implements a cache strategy for botanical names so no method has to be overwritten by
45 * a subclass for botanic names.
46 * Where differing from this default botanic name strategy other subclasses should overwrite the
47 * existing methods, e.g. a CacheStrategy for zoological names should overwrite getAuthorAndExAuthor
48 * @author a.mueller
49 */
50 public class TaxonNameDefaultCacheStrategy
51 extends NameCacheStrategyBase
52 implements INonViralNameCacheStrategy {
53
54 private static final Logger logger = Logger.getLogger(TaxonNameDefaultCacheStrategy.class);
55 private static final long serialVersionUID = -6577757501563212669L;
56
57 final static UUID uuid = UUID.fromString("1cdda0d1-d5bc-480f-bf08-40a510a2f223");
58
59 protected String nameAuthorSeperator = " ";
60 protected String basionymStart = "(";
61 protected String basionymEnd = ")";
62 protected String exAuthorSeperator = " ex ";
63 private static String NOTHO = "notho";
64 protected CharSequence basionymAuthorCombinationAuthorSeperator = " ";
65
66 protected String zooAuthorYearSeperator = ", ";
67
68 @Override
69 public UUID getUuid(){
70 return uuid;
71 }
72
73 // ************************** FACTORY ******************************/
74
75 public static TaxonNameDefaultCacheStrategy NewInstance(){
76 return new TaxonNameDefaultCacheStrategy();
77 }
78
79
80 // ************ CONSTRUCTOR *******************/
81
82 protected TaxonNameDefaultCacheStrategy(){
83 super();
84 }
85
86 /* **************** GETTER / SETTER **************************************/
87
88 /**
89 * String that separates the NameCache part from the AuthorCache part
90 * @return
91 */
92 public String getNameAuthorSeperator() {
93 return nameAuthorSeperator;
94 }
95 public void setNameAuthorSeperator(String nameAuthorSeperator) {
96 this.nameAuthorSeperator = nameAuthorSeperator;
97 }
98
99
100 /**
101 * String the basionym author part starts with e.g. '('.
102 * This should correspond with the {@link TaxonNameDefaultCacheStrategy#getBasionymEnd() basionymEnd} attribute
103 * @return
104 */
105 public String getBasionymStart() {
106 return basionymStart;
107 }
108 public void setBasionymStart(String basionymStart) {
109 this.basionymStart = basionymStart;
110 }
111
112 /**
113 * String the basionym author part ends with e.g. ')'.
114 * This should correspond with the {@link TaxonNameDefaultCacheStrategy#getBasionymStart() basionymStart} attribute
115 * @return
116 */
117 public String getBasionymEnd() {
118 return basionymEnd;
119 }
120 public void setBasionymEnd(String basionymEnd) {
121 this.basionymEnd = basionymEnd;
122 }
123
124
125 /**
126 * String to separate ex author from author.
127 * @return
128 */
129 public String getExAuthorSeperator() {
130 return exAuthorSeperator;
131 }
132 public void setExAuthorSeperator(String exAuthorSeperator) {
133 this.exAuthorSeperator = exAuthorSeperator;
134 }
135
136
137 /**
138 * String that separates the basionym/original_combination author part from the combination author part
139 * @return
140 */
141 public CharSequence getBasionymAuthorCombinationAuthorSeperator() {
142 return basionymAuthorCombinationAuthorSeperator;
143 }
144
145
146 public void setBasionymAuthorCombinationAuthorSeperator( CharSequence basionymAuthorCombinationAuthorSeperator) {
147 this.basionymAuthorCombinationAuthorSeperator = basionymAuthorCombinationAuthorSeperator;
148 }
149
150 public String getZooAuthorYearSeperator() {
151 return zooAuthorYearSeperator;
152 }
153 public void setZooAuthorYearSeperator(String authorYearSeperator) {
154 this.zooAuthorYearSeperator = authorYearSeperator;
155 }
156
157 // ******************* Authorship ******************************/
158
159 @Override
160 public String getAuthorshipCache(TaxonName taxonName) {
161 if (taxonName == null){
162 return null;
163 }else if (taxonName.getNameType().isViral()){
164 return null;
165 }else if(taxonName.isProtectedAuthorshipCache() == true) {
166 //cache protected
167 return taxonName.getAuthorshipCache();
168 }else{
169 return getNonCacheAuthorshipCache(taxonName);
170 }
171 }
172
173 /**
174 * Returns the authorshipcache string for the atomized authorship fields. Does not use the authorship field.
175 * @throws NullPointerException if nonViralName is null.
176 * @param taxonName
177 * @return
178 */
179 protected String getNonCacheAuthorshipCache(TaxonName nonViralName){
180 if (nonViralName.getNameType().isZoological()){
181 return this.getZoologicalNonCacheAuthorshipCache(nonViralName);
182 }else{
183 String result = "";
184 INomenclaturalAuthor combinationAuthor = nonViralName.getCombinationAuthorship();
185 INomenclaturalAuthor exCombinationAuthor = nonViralName.getExCombinationAuthorship();
186 INomenclaturalAuthor basionymAuthor = nonViralName.getBasionymAuthorship();
187 INomenclaturalAuthor exBasionymAuthor = nonViralName.getExBasionymAuthorship();
188 String basionymPart = "";
189 String authorPart = "";
190 //basionym
191 if (basionymAuthor != null || exBasionymAuthor != null){
192 basionymPart = basionymStart + getAuthorAndExAuthor(basionymAuthor, exBasionymAuthor) + basionymEnd;
193 }
194 if (combinationAuthor != null || exCombinationAuthor != null){
195 authorPart = getAuthorAndExAuthor(combinationAuthor, exCombinationAuthor);
196 }
197 result = CdmUtils.concat(basionymAuthorCombinationAuthorSeperator, basionymPart, authorPart);
198 // if ("".equals(result)){
199 // result = null;
200 // }
201 return result;
202 }
203 }
204
205 protected String getZoologicalNonCacheAuthorshipCache(TaxonName nonViralName) {
206 if (nonViralName == null){
207 return null;
208 }
209 String result = "";
210 INomenclaturalAuthor combinationAuthor = nonViralName.getCombinationAuthorship();
211 INomenclaturalAuthor exCombinationAuthor = nonViralName.getExCombinationAuthorship();
212 INomenclaturalAuthor basionymAuthor = nonViralName.getBasionymAuthorship();
213 INomenclaturalAuthor exBasionymAuthor = nonViralName.getExBasionymAuthorship();
214 Integer publicationYear = nonViralName.getPublicationYear();
215 Integer originalPublicationYear = nonViralName.getOriginalPublicationYear();
216
217 String basionymPart = "";
218 String authorPart = "";
219 //basionym
220 if (basionymAuthor != null || exBasionymAuthor != null || originalPublicationYear != null ){
221 String authorAndEx = getAuthorAndExAuthor(basionymAuthor, exBasionymAuthor);
222 String originalPublicationYearString = originalPublicationYear == null ? null : String.valueOf(originalPublicationYear);
223 String authorAndExAndYear = CdmUtils.concat(zooAuthorYearSeperator, authorAndEx, originalPublicationYearString );
224 basionymPart = basionymStart + authorAndExAndYear + basionymEnd;
225 }
226 if (combinationAuthor != null || exCombinationAuthor != null){
227 String authorAndEx = getAuthorAndExAuthor(combinationAuthor, exCombinationAuthor);
228 String publicationYearString = publicationYear == null ? null : String.valueOf(publicationYear);
229 authorPart = CdmUtils.concat(zooAuthorYearSeperator, authorAndEx, publicationYearString);
230 }
231 result = CdmUtils.concat(basionymAuthorCombinationAuthorSeperator, basionymPart, authorPart);
232 if (result == null){
233 result = "";
234 }
235 return result;
236 }
237
238
239 /**
240 * Returns the AuthorCache part for a combination of an author and an ex author. This applies on
241 * combination authors as well as on basionym/orginal combination authors.
242 * The correct order is exAuthor ex author though some botanist do not know about and do it the
243 * other way round. (see 46.4-46.6 ICBN (Vienna Code, 2006))
244 *
245 * @param author the author
246 * @param exAuthor the ex-author
247 * @return
248 */
249 protected String getAuthorAndExAuthor(INomenclaturalAuthor author, INomenclaturalAuthor exAuthor){
250 String authorString = "";
251 String exAuthorString = "";
252 if (author != null){
253 authorString = CdmUtils.Nz(author.getNomenclaturalTitle());
254 }
255 if (exAuthor != null){
256 exAuthorString = CdmUtils.Nz(exAuthor.getNomenclaturalTitle());
257 exAuthorString += exAuthorSeperator;
258 }
259 String result = exAuthorString + authorString;
260 return result;
261 }
262
263
264
265 /**
266 * Checks if the given name should include the author in it's cached version.<BR>
267 * This is usually the case but not for <i>species aggregates</i>.
268 * @param nonViralName
269 * @return
270 */
271 protected boolean nameIncludesAuthorship(INonViralName nonViralName){
272 Rank rank = nonViralName.getRank();
273 if (rank != null && rank.isSpeciesAggregate()){
274 return false;
275 }else{
276 return true;
277 }
278 }
279
280 // ************* TAGGED NAME ***************************************/
281
282 @Override
283 public List<TaggedText> getTaggedFullTitle(TaxonName nonViralName) {
284 List<TaggedText> tags = new ArrayList<>();
285
286 //null
287 if (nonViralName == null){
288 return null;
289 }
290
291 //protected full title cache
292 if (nonViralName.isProtectedFullTitleCache()){
293 tags.add(new TaggedText(TagEnum.fullName, nonViralName.getFullTitleCache()));
294 return tags;
295 }
296
297 //title cache
298 // String titleCache = nonViralName.getTitleCache();
299 List<TaggedText> titleTags = getTaggedTitle(nonViralName);
300 tags.addAll(titleTags);
301
302 //reference
303 String microReference = nonViralName.getNomenclaturalMicroReference();
304 INomenclaturalReference ref = nonViralName.getNomenclaturalReference();
305 String referenceCache = null;
306 if (ref != null){
307 Reference reference = HibernateProxyHelper.deproxy(ref, Reference.class);
308 referenceCache = reference.getNomenclaturalCitation(microReference);
309 }
310 //add to tags
311 if (referenceCache!= null && isNotBlank(referenceCache)){
312 if (! referenceCache.trim().startsWith("in ")){
313 String refConcat = ", ";
314 tags.add(new TaggedText(TagEnum.separator, refConcat));
315 }
316 tags.add(new TaggedText(TagEnum.reference, referenceCache));
317 }
318
319 addOriginalSpelling(tags, nonViralName);
320
321 //nomenclatural status
322 tags.addAll(getNomStatusTags(nonViralName, true, false));
323 return tags;
324 }
325
326 @Override
327 public List<TaggedText> getNomStatusTags(TaxonName nonViralName, boolean includeSeparatorBefore,
328 boolean includeSeparatorAfter) {
329
330 Set<NomenclaturalStatus> ncStati = nonViralName.getStatus();
331 Iterator<NomenclaturalStatus> iterator = ncStati.iterator();
332 List<TaggedText> nomStatusTags = new ArrayList<>();
333 while (iterator.hasNext()) {
334 NomenclaturalStatus ncStatus = iterator.next();
335 // since the NewInstance method of nomencatural status allows null as parameter
336 // we have to check for null values here
337 String nomStatusStr = "not defined";
338 if(ncStatus.getType() != null){
339 NomenclaturalStatusType statusType = ncStatus.getType();
340 List<Language> prefLangs = Arrays.asList(new Language[]{Language.LATIN(), Language.DEFAULT()});
341 Representation repr = statusType.getPreferredRepresentation(prefLangs);
342 if (repr != null){
343 if(!Language.LATIN().equals(repr.getLanguage())){
344 String message = "No latin representation available for nom. status. " + statusType.getTitleCache();
345 logger.info(message);
346 }
347 nomStatusStr = repr.getAbbreviatedLabel();
348 }else{
349 String message = "No representation available for nom. status. " + statusType.getTitleCache();
350 logger.warn(message);
351 nomStatusStr = statusType.getTitleCache();
352 }
353 }else if(isNotBlank(ncStatus.getRuleConsidered())){
354 nomStatusStr = ncStatus.getRuleConsidered();
355 }
356 String statusSeparator = ", ";
357 if (includeSeparatorBefore){
358 nomStatusTags.add(new TaggedText(TagEnum.separator, statusSeparator));
359 }
360 nomStatusTags.add(new TaggedText(TagEnum.nomStatus, nomStatusStr, new TypedEntityReference<>(ncStatus.getClass(), ncStatus.getUuid())));
361 if (includeSeparatorAfter){
362 nomStatusTags.add(new TaggedText(TagEnum.postSeparator, ","));
363 }
364 }
365 return nomStatusTags;
366 }
367
368 @Override
369 public List<TaggedText> getTaggedTitle(TaxonName taxonName) {
370 if (taxonName == null){
371 return null;
372 }
373
374 List<TaggedText> tags = new ArrayList<>();
375
376 if (taxonName.isViral()){
377 return getViralTaggedTitle(taxonName);
378 }
379 //TODO how to handle protected fullTitleCache here?
380 if (taxonName.isProtectedTitleCache()){
381 //protected title cache
382 tags.add(new TaggedText(TagEnum.name, taxonName.getTitleCache()));
383 return tags;
384 }else if (taxonName.isHybridFormula()){
385 //hybrid formula
386 String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
387 boolean isFirst = true;
388 List<HybridRelationship> rels = taxonName.getOrderedChildRelationships();
389 for (HybridRelationship rel: rels){
390 if (! isFirst){
391 tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
392 }
393 isFirst = false;
394 tags.addAll(getTaggedTitle(rel.getParentName()));
395 }
396 return tags;
397 }else if (taxonName.isAutonym()){
398 //Autonym
399 tags.addAll(handleTaggedAutonym(taxonName));
400 }else{ //not Autonym
401 List<TaggedText> nameTags = getTaggedName(taxonName);
402 tags.addAll(nameTags);
403 String authorCache = getAuthorshipCache(taxonName);
404 if (isNotBlank(authorCache)){
405 tags.add(new TaggedText(TagEnum.authors, authorCache));
406 }
407 }
408
409 return tags;
410 }
411
412 /**
413 * @param taxonName
414 * @return
415 */
416 private List<TaggedText> getViralTaggedTitle(TaxonName viralName) {
417 List<TaggedText> tags = new ArrayList<>();
418 if (viralName.isProtectedTitleCache()){
419 //protected title cache
420 tags.add(new TaggedText(TagEnum.name, viralName.getTitleCache()));
421 return tags;
422 }else{
423 if (isNotBlank(viralName.getAcronym())){
424 //this is not according to the code
425 tags.add(new TaggedText(TagEnum.name, viralName.getAcronym()));
426 }
427 return tags;
428 }
429 }
430
431 /**
432 * Returns the tag list of the name part (without author and reference).
433 * @param nonViralName
434 * @return
435 */
436 @Override
437 public List<TaggedText> getTaggedName(TaxonName nonViralName) {
438 if (nonViralName == null){
439 return null;
440 }else if (nonViralName.isViral()){
441 return null;
442 }
443 List<TaggedText> tags = new ArrayList<>();
444 Rank rank = nonViralName.getRank();
445
446 if (nonViralName.isProtectedNameCache()){
447 tags.add(new TaggedText(TagEnum.name, nonViralName.getNameCache()));
448 }else if (nonViralName.isHybridFormula()){
449 //hybrid formula
450 String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
451 boolean isFirst = true;
452 List<HybridRelationship> rels = nonViralName.getOrderedChildRelationships();
453 for (HybridRelationship rel: rels){
454 if (! isFirst){
455 tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
456 }
457 isFirst = false;
458 tags.addAll(getTaggedName(rel.getParentName()));
459 }
460 return tags;
461
462 }else if (rank == null){
463 tags = getRanklessTaggedNameCache(nonViralName);
464 // }else if (nonViralName.isInfragenericUnranked()){
465 // result = getUnrankedInfragenericNameCache(nonViralName);
466 }else if (rank.isInfraSpecific()){
467 tags = getInfraSpeciesTaggedNameCache(nonViralName);
468 }else if (rank.isSpecies() || isAggregateWithAuthorship(nonViralName, rank) ){ //exception see #4288
469 tags = getSpeciesTaggedNameCache(nonViralName);
470 }else if (rank.isInfraGeneric()){
471 tags = getInfraGenusTaggedNameCache(nonViralName);
472 }else if (rank.isGenus()){
473 tags = getGenusOrUninomialTaggedNameCache(nonViralName);
474 }else if (rank.isSupraGeneric()){
475 tags = getGenusOrUninomialTaggedNameCache(nonViralName);
476 }else{
477 tags = getRanklessTaggedNameCache(nonViralName);
478 logger.warn("Rank does not belong to a rank class: " + rank.getTitleCache() + ". Used rankless nameCache for name " + nonViralName.getUuid());
479 }
480 //TODO handle appended phrase here instead of different places, check first if this is true for all
481 //cases
482
483 return tags;
484
485 }
486
487
488
489
490 //***************************** PRIVATES ***************************************/
491
492
493 private boolean isAggregateWithAuthorship(TaxonName nonViralName, Rank rank) {
494 if (rank == null){
495 return false;
496 }else{
497 return rank.isSpeciesAggregate() && ( isNotBlank(nonViralName.getAuthorshipCache()) || nonViralName.getNomenclaturalReference() != null );
498 }
499 }
500
501
502 /**
503 * Returns the tag list for an autonym taxon.
504 *
505 * @see NonViralName#isAutonym()
506 * @see BotanicalName#isAutonym()
507 * @param nonViralName
508 * @return
509 */
510 private List<TaggedText> handleTaggedAutonym(TaxonName nonViralName) {
511 List<TaggedText> tags = null;
512 if (nonViralName.isInfraSpecific()){
513 //species part
514 tags = getSpeciesTaggedNameCache(nonViralName);
515
516 //author
517 String authorCache = getAuthorshipCache(nonViralName);
518 if (isNotBlank(authorCache)){
519 tags.add(new TaggedText(TagEnum.authors, authorCache));
520 }
521
522 //infra species marker
523 if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraSpecific()){
524 //TODO handle exception
525 logger.warn("Rank for autonym does not exist or is not lower than species !!");
526 }else{
527 String infraSpeciesMarker = nonViralName.getRank().getAbbreviation();
528 if (nonViralName.isTrinomHybrid()){
529 infraSpeciesMarker = CdmUtils.concat("", NOTHO, infraSpeciesMarker);
530 }
531 if (isNotBlank(infraSpeciesMarker)){
532 tags.add(new TaggedText(TagEnum.rank, infraSpeciesMarker));
533 }
534 }
535
536 //infra species
537 String infraSpeciesPart = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet()).trim();
538 if (isNotBlank(infraSpeciesPart)){
539 tags.add(new TaggedText(TagEnum.name, infraSpeciesPart));
540 }
541
542 } else if (nonViralName.isInfraGeneric()){
543 //genus part
544 tags =getGenusOrUninomialTaggedNameCache(nonViralName);
545
546 //author
547 String authorCache = getAuthorshipCache(nonViralName);
548 if (isNotBlank(authorCache)){
549 tags.add(new TaggedText(TagEnum.authors, authorCache));
550 }
551
552 //infra species marker
553 if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraGeneric()){
554 //TODO handle exception
555 logger.warn("Rank for autonym does not exist or is not lower than species !!");
556 }else{
557 Rank rank = nonViralName.getRank();
558 String infraGenericMarker = rank.getAbbreviation();
559 if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
560 infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
561 }
562 if (isNotBlank(infraGenericMarker)){
563 tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
564 }
565 }
566
567 //infra genus
568 String infraGenericPart = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
569 if (isNotBlank(infraGenericPart)){
570 tags.add(new TaggedText(TagEnum.name, infraGenericPart));
571 }
572 }
573
574 return tags;
575 }
576
577
578
579 /**
580 * Returns the tag list for rankless taxa.
581 * @param nonViralName
582 * @return
583 */
584 protected List<TaggedText> getRanklessTaggedNameCache(INonViralName nonViralName){
585 List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
586 String speciesEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
587 if (isNotBlank(speciesEpi)){
588 tags.add(new TaggedText(TagEnum.name, speciesEpi));
589 }
590
591 String infraSpeciesEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
592 if (isNotBlank(infraSpeciesEpi)){
593 tags.add(new TaggedText(TagEnum.name, infraSpeciesEpi));
594 }
595
596 //result += " (rankless)";
597 addAppendedTaggedPhrase(tags, nonViralName);
598 return tags;
599 }
600
601 /**
602 * Returns the tag list for the first epithet (including a hybrid sign if required).
603 * @param nonViralName
604 * @return
605 */
606 private List<TaggedText> getUninomialTaggedPart(INonViralName nonViralName) {
607 List<TaggedText> tags = new ArrayList<>();
608
609 if (nonViralName.isMonomHybrid()){
610 addHybridPrefix(tags);
611 }
612
613 String uninomial = CdmUtils.Nz(nonViralName.getGenusOrUninomial()).trim();
614 if (isNotBlank(uninomial)){
615 tags.add(new TaggedText(TagEnum.name, uninomial));
616 }
617
618 return tags;
619 }
620
621 /**
622 * Returns the tag list for an genus or higher taxon.
623 *
624 * @param nonViralName
625 * @return
626 */
627 protected List<TaggedText> getGenusOrUninomialTaggedNameCache(INonViralName nonViralName){
628 List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
629 addAppendedTaggedPhrase(tags, nonViralName);
630 return tags;
631 }
632
633 /**
634 * Returns the tag list for an infrageneric taxon (including species aggregates).
635 *
636 * @see #getSpeciesAggregateTaggedCache(NonViralName)
637 * @param nonViralName
638 * @return
639 */
640 protected List<TaggedText> getInfraGenusTaggedNameCache(INonViralName nonViralName){
641 Rank rank = nonViralName.getRank();
642 if (rank != null && rank.isSpeciesAggregate() && isBlank(nonViralName.getAuthorshipCache())){
643 return getSpeciesAggregateTaggedCache(nonViralName);
644 }
645
646 //genus
647 List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
648
649 //marker
650 String infraGenericMarker;
651 if (rank != null){
652 try {
653 infraGenericMarker = rank.getInfraGenericMarker();
654 if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
655 infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
656 }
657 } catch (UnknownCdmTypeException e) {
658 infraGenericMarker = "'unhandled infrageneric rank'";
659 }
660 }else{
661 infraGenericMarker = "'undefined infrageneric rank'";
662 }
663 String infraGenEpi = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
664 if (nonViralName.isBinomHybrid()){
665 infraGenericMarker = CdmUtils.concat("", NOTHO, infraGenericMarker);
666 }
667
668 addInfraGenericPart(nonViralName, tags, infraGenericMarker, infraGenEpi);
669
670 addAppendedTaggedPhrase(tags, nonViralName);
671 return tags;
672 }
673
674
675 /**
676 * Default implementation for the infrageneric part of a name.
677 * This is usually the infrageneric marker and the infrageneric epitheton. But may be
678 * implemented differently e.g. for zoological names the infrageneric epitheton
679 * may be surrounded by brackets and the marker left out.
680 * @param nonViralName
681 * @param tags
682 * @param infraGenericMarker
683 */
684 protected void addInfraGenericPart(
685 @SuppressWarnings("unused") INonViralName name,
686 List<TaggedText> tags,
687 String infraGenericMarker,
688 String infraGenEpi) {
689 //add marker
690 tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
691
692 //add epitheton
693 if (isNotBlank(infraGenEpi)){
694 tags.add(new TaggedText(TagEnum.name, infraGenEpi));
695 }
696 }
697
698 /**
699 * Returns the tag list for a species aggregate (or similar) taxon.<BR>
700 * Possible ranks for a <i>species aggregate</i> are "aggr.", "species group", ...
701 * @param nonViralName
702 * @return
703 */
704 protected List<TaggedText> getSpeciesAggregateTaggedCache(INonViralName nonViralName){
705 if (! isBlank(nonViralName.getAuthorshipCache())){
706 List<TaggedText> result = getSpeciesTaggedNameCache(nonViralName);
707 return result;
708 }
709
710
711 List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
712
713 addSpeciesAggregateTaggedEpithet(tags, nonViralName);
714 addAppendedTaggedPhrase(tags, nonViralName);
715 return tags;
716 }
717
718 /**
719 * Adds the aggregate tag to the tag list.
720 * @param tags
721 * @param nonViralName
722 */
723 private void addSpeciesAggregateTaggedEpithet(List<TaggedText> tags, INonViralName nonViralName) {
724 String marker;
725 try {
726 marker = nonViralName.getRank().getInfraGenericMarker();
727 } catch (UnknownCdmTypeException e) {
728 marker = "'unknown aggregat type'";
729 }
730 if (isNotBlank(marker)){
731 tags.add(new TaggedText(TagEnum.rank, marker));
732 }
733 }
734
735
736 /**
737 * Returns the tag list for a species taxon.
738 * @param nonViralName
739 * @return
740 */
741 protected List<TaggedText> getSpeciesTaggedNameCache(INonViralName nonViralName){
742 List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
743 addAppendedTaggedPhrase(tags, nonViralName);
744 return tags;
745 }
746
747 protected List<TaggedText> getInfraSpeciesTaggedNameCache(TaxonName name){
748 if (name.getNameType().isZoological()){
749 boolean includeMarker =includeInfraSpecificMarkerForZooNames(name);
750 return getInfraSpeciesTaggedNameCache(name, includeMarker);
751 }else{
752 return getInfraSpeciesTaggedNameCache(name, true);
753 }
754 }
755
756 protected boolean includeInfraSpecificMarkerForZooNames(TaxonName name){
757 return ! (name.isAutonym()); //only exclude marker if autonym, see also ZooNameNoMarkerCacheStrategy
758 }
759
760 /**
761 * Creates the tag list for an infraspecific taxon. If include is true the result will contain
762 * the infraspecific marker (e.g. "var.")
763 * @param nonViralName
764 * @param includeMarker
765 * @return
766 */
767 protected List<TaggedText> getInfraSpeciesTaggedNameCache(INonViralName nonViralName, boolean includeMarker){
768 List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
769 if (includeMarker || nonViralName.isTrinomHybrid()){
770 String marker = (nonViralName.getRank().getAbbreviation()).trim().replace("null", "");
771 if (nonViralName.isTrinomHybrid()){
772 marker = CdmUtils.concat("", NOTHO, marker);
773 }
774 if (isNotBlank(marker)){
775 tags.add(new TaggedText(TagEnum.rank, marker));
776 }
777
778 }
779 String infrSpecEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
780
781 infrSpecEpi = infrSpecEpi.trim().replace("null", "");
782
783 if (isNotBlank(infrSpecEpi)){
784 tags.add(new TaggedText(TagEnum.name, infrSpecEpi));
785 }
786
787 addAppendedTaggedPhrase(tags, nonViralName);
788 return tags;
789 }
790
791
792 /**
793 * Adds a tag for the hybrid sign and an empty separator to avoid trailing whitespaces.
794 * @param tags
795 */
796 private void addHybridPrefix(List<TaggedText> tags) {
797 tags.add(new TaggedText(TagEnum.hybridSign, NonViralNameParserImplRegExBase.hybridSign));
798 tags.add(new TaggedText(TagEnum.separator, "")); //no whitespace separator
799 }
800
801 /**
802 * Creates the tag list for the genus and species part.
803 * @param nonViralName
804 * @return
805 */
806 private List<TaggedText> getGenusAndSpeciesTaggedPart(INonViralName nonViralName) {
807 //Uninomial
808 List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
809
810 //InfraGenericEpi
811 boolean hasInfraGenericEpi = isNotBlank(nonViralName.getInfraGenericEpithet());
812 if (hasInfraGenericEpi){
813 String infrGenEpi = nonViralName.getInfraGenericEpithet().trim();
814 if (nonViralName.isBinomHybrid()){
815 //maybe not correct but not really expected to happen
816 infrGenEpi = UTF8.HYBRID + infrGenEpi;
817 }
818 infrGenEpi = "(" + infrGenEpi + ")";
819 tags.add(new TaggedText(TagEnum.name, infrGenEpi));
820 }
821
822 //Species Epi
823 String specEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
824 if (! hasInfraGenericEpi && nonViralName.isBinomHybrid() ||
825 hasInfraGenericEpi && nonViralName.isTrinomHybrid()){
826 addHybridPrefix(tags);
827 }
828 if (isNotBlank(specEpi)){
829 tags.add(new TaggedText(TagEnum.name, specEpi));
830 }
831 return tags;
832 }
833
834 /**
835 * Adds the tag for the appended phrase if an appended phrase exists
836 * @param tags
837 * @param nonViralName
838 */
839 protected void addAppendedTaggedPhrase(List<TaggedText> tags, INonViralName nonViralName){
840 String appendedPhrase = nonViralName ==null ? null : nonViralName.getAppendedPhrase();
841 if (isNotBlank(appendedPhrase)){
842 tags.add(new TaggedText(TagEnum.name, appendedPhrase));
843 }
844 }
845
846 @Override
847 public String getLastEpithet(TaxonName taxonName) {
848 Rank rank = taxonName.getRank();
849 if(rank.isGenus() || rank.isSupraGeneric()) {
850 return taxonName.getGenusOrUninomial();
851 } else if(rank.isInfraGeneric()) {
852 return taxonName.getInfraGenericEpithet();
853 } else if(rank.isSpecies()) {
854 return taxonName.getSpecificEpithet();
855 } else {
856 return taxonName.getInfraSpecificEpithet();
857 }
858 }
859
860 @Override
861 protected List<TaggedText> doGetTaggedTitle(TaxonName taxonName) {
862 List<TaggedText> tags = new ArrayList<>();
863 if (taxonName.getNameType().isViral()){
864 String acronym = taxonName.getAcronym();
865 tags.add(new TaggedText(TagEnum.name, acronym));
866 return tags;
867 }else if (taxonName.isHybridFormula()){
868 //hybrid formula
869 String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
870 boolean isFirst = true;
871 List<HybridRelationship> rels = taxonName.getOrderedChildRelationships();
872 for (HybridRelationship rel: rels){
873 if (! isFirst){
874 tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
875 }
876 isFirst = false;
877 tags.addAll(getTaggedTitle(rel.getParentName()));
878 }
879 return tags;
880 }else if (taxonName.isAutonym()){
881 //Autonym
882 tags.addAll(handleTaggedAutonym(taxonName));
883 }else{ //not Autonym
884 List<TaggedText> nameTags = getTaggedName(taxonName);
885 tags.addAll(nameTags);
886 String authorCache = getAuthorshipCache(taxonName);
887 if (isNotBlank(authorCache)){
888 tags.add(new TaggedText(TagEnum.authors, authorCache));
889 }
890 }
891 return tags;
892 }
893
894 }