ref #1444, ref #8509 improve ZoologicalNameCacheStrategy for infraspecific ranks...
[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.Iterator;
13 import java.util.List;
14 import java.util.Set;
15 import java.util.UUID;
16
17 import org.apache.commons.lang.StringUtils;
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 (StringUtils.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
327
328 /**
329 * @param nonViralName
330 * @param tags
331 * @return
332 */
333 @Override
334 public List<TaggedText> getNomStatusTags(TaxonName nonViralName, boolean includeSeparatorBefore,
335 boolean includeSeparatorAfter) {
336
337 Set<NomenclaturalStatus> ncStati = nonViralName.getStatus();
338 Iterator<NomenclaturalStatus> iterator = ncStati.iterator();
339 List<TaggedText> nomStatusTags = new ArrayList<>();
340 while (iterator.hasNext()) {
341 NomenclaturalStatus ncStatus = iterator.next();
342 // since the NewInstance method of nomencatural status allows null as parameter
343 // we have to check for null values here
344 String nomStatusStr = "not defined";
345 if(ncStatus.getType() != null){
346 NomenclaturalStatusType statusType = ncStatus.getType();
347 Language lang = Language.LATIN();
348 Representation repr = statusType.getRepresentation(lang);
349 if (repr != null){
350 nomStatusStr = repr.getAbbreviatedLabel();
351 }else{
352 String message = "No latin representation available for nom. status. " + statusType.getTitleCache();
353 logger.warn(message);
354 throw new IllegalStateException(message);
355 }
356 }else if(StringUtils.isNotBlank(ncStatus.getRuleConsidered())){
357 nomStatusStr = ncStatus.getRuleConsidered();
358 }
359 String statusSeparator = ", ";
360 if (includeSeparatorBefore){
361 nomStatusTags.add(new TaggedText(TagEnum.separator, statusSeparator));
362 }
363 nomStatusTags.add(new TaggedText(TagEnum.nomStatus, nomStatusStr, new TypedEntityReference<>(ncStatus.getClass(), ncStatus.getUuid())));
364 if (includeSeparatorAfter){
365 nomStatusTags.add(new TaggedText(TagEnum.postSeparator, ","));
366 }
367 }
368 return nomStatusTags;
369 }
370
371 @Override
372 public List<TaggedText> getTaggedTitle(TaxonName taxonName) {
373 if (taxonName == null){
374 return null;
375 }
376
377 List<TaggedText> tags = new ArrayList<>();
378
379 if (taxonName.isViral()){
380 return getViralTaggedTitle(taxonName);
381 }
382 //TODO how to handle protected fullTitleCache here?
383 if (taxonName.isProtectedTitleCache()){
384 //protected title cache
385 tags.add(new TaggedText(TagEnum.name, taxonName.getTitleCache()));
386 return tags;
387 }else if (taxonName.isHybridFormula()){
388 //hybrid formula
389 String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
390 boolean isFirst = true;
391 List<HybridRelationship> rels = taxonName.getOrderedChildRelationships();
392 for (HybridRelationship rel: rels){
393 if (! isFirst){
394 tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
395 }
396 isFirst = false;
397 tags.addAll(getTaggedTitle(rel.getParentName()));
398 }
399 return tags;
400 }else if (taxonName.isAutonym()){
401 //Autonym
402 tags.addAll(handleTaggedAutonym(taxonName));
403 }else{ //not Autonym
404 List<TaggedText> nameTags = getTaggedName(taxonName);
405 tags.addAll(nameTags);
406 String authorCache = getAuthorshipCache(taxonName);
407 if (StringUtils.isNotBlank(authorCache)){
408 tags.add(new TaggedText(TagEnum.authors, authorCache));
409 }
410 }
411
412 return tags;
413 }
414
415 /**
416 * @param taxonName
417 * @return
418 */
419 private List<TaggedText> getViralTaggedTitle(TaxonName viralName) {
420 List<TaggedText> tags = new ArrayList<>();
421 if (viralName.isProtectedTitleCache()){
422 //protected title cache
423 tags.add(new TaggedText(TagEnum.name, viralName.getTitleCache()));
424 return tags;
425 }else{
426 if (StringUtils.isNotBlank(viralName.getAcronym())){
427 //this is not according to the code
428 tags.add(new TaggedText(TagEnum.name, viralName.getAcronym()));
429 }
430 return tags;
431 }
432 }
433
434 /**
435 * Returns the tag list of the name part (without author and reference).
436 * @param nonViralName
437 * @return
438 */
439 @Override
440 public List<TaggedText> getTaggedName(TaxonName nonViralName) {
441 if (nonViralName == null){
442 return null;
443 }else if (nonViralName.getNameType().isViral()){
444 return null;
445 }
446 List<TaggedText> tags = new ArrayList<>();
447 Rank rank = nonViralName.getRank();
448
449 if (nonViralName.isProtectedNameCache()){
450 tags.add(new TaggedText(TagEnum.name, nonViralName.getNameCache()));
451 }else if (nonViralName.isHybridFormula()){
452 //hybrid formula
453 String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
454 boolean isFirst = true;
455 List<HybridRelationship> rels = nonViralName.getOrderedChildRelationships();
456 for (HybridRelationship rel: rels){
457 if (! isFirst){
458 tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
459 }
460 isFirst = false;
461 tags.addAll(getTaggedName(rel.getParentName()));
462 }
463 return tags;
464
465 }else if (rank == null){
466 tags = getRanklessTaggedNameCache(nonViralName);
467 // }else if (nonViralName.isInfragenericUnranked()){
468 // result = getUnrankedInfragenericNameCache(nonViralName);
469 }else if (rank.isInfraSpecific()){
470 tags = getInfraSpeciesTaggedNameCache(nonViralName);
471 }else if (rank.isSpecies() || isAggregateWithAuthorship(nonViralName, rank) ){ //exception see #4288
472 tags = getSpeciesTaggedNameCache(nonViralName);
473 }else if (rank.isInfraGeneric()){
474 tags = getInfraGenusTaggedNameCache(nonViralName);
475 }else if (rank.isGenus()){
476 tags = getGenusOrUninomialTaggedNameCache(nonViralName);
477 }else if (rank.isSupraGeneric()){
478 tags = getGenusOrUninomialTaggedNameCache(nonViralName);
479 }else{
480 tags = getRanklessTaggedNameCache(nonViralName);
481 logger.warn("Rank does not belong to a rank class: " + rank.getTitleCache() + ". Used rankless nameCache for name " + nonViralName.getUuid());
482 }
483 //TODO handle appended phrase here instead of different places, check first if this is true for all
484 //cases
485
486 return tags;
487
488 }
489
490
491
492
493 //***************************** PRIVATES ***************************************/
494
495
496 private boolean isAggregateWithAuthorship(TaxonName nonViralName, Rank rank) {
497 if (rank == null){
498 return false;
499 }else{
500 return rank.isSpeciesAggregate() && ( isNotBlank(nonViralName.getAuthorshipCache()) || nonViralName.getNomenclaturalReference() != null );
501 }
502 }
503
504
505 /**
506 * Returns the tag list for an autonym taxon.
507 *
508 * @see NonViralName#isAutonym()
509 * @see BotanicalName#isAutonym()
510 * @param nonViralName
511 * @return
512 */
513 private List<TaggedText> handleTaggedAutonym(TaxonName nonViralName) {
514 List<TaggedText> tags = null;
515 if (nonViralName.isInfraSpecific()){
516 //species part
517 tags = getSpeciesTaggedNameCache(nonViralName);
518
519 //author
520 String authorCache = getAuthorshipCache(nonViralName);
521 if (StringUtils.isNotBlank(authorCache)){
522 tags.add(new TaggedText(TagEnum.authors, authorCache));
523 }
524
525
526 //infra species marker
527 if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraSpecific()){
528 //TODO handle exception
529 logger.warn("Rank for autonym does not exist or is not lower than species !!");
530 }else{
531 String infraSpeciesMarker = nonViralName.getRank().getAbbreviation();
532 if (nonViralName.isTrinomHybrid()){
533 infraSpeciesMarker = CdmUtils.concat("", NOTHO, infraSpeciesMarker);
534 }
535 if (StringUtils.isNotBlank(infraSpeciesMarker)){
536 tags.add(new TaggedText(TagEnum.rank, infraSpeciesMarker));
537 }
538 }
539
540 //infra species
541 String infraSpeciesPart = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet()).trim();
542 if (StringUtils.isNotBlank(infraSpeciesPart)){
543 tags.add(new TaggedText(TagEnum.name, infraSpeciesPart));
544 }
545
546 } else if (nonViralName.isInfraGeneric()){
547 //genus part
548 tags =getGenusOrUninomialTaggedNameCache(nonViralName);
549
550
551 //infra species marker
552 if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraGeneric()){
553 //TODO handle exception
554 logger.warn("Rank for autonym does not exist or is not lower than species !!");
555 }else{
556 Rank rank = nonViralName.getRank();
557 String infraGenericMarker = rank.getAbbreviation();
558 if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
559 infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
560 }
561 if (StringUtils.isNotBlank(infraGenericMarker)){
562 tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
563 }
564 }
565
566 //infra species
567 String infraGenericPart = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
568 if (StringUtils.isNotBlank(infraGenericPart)){
569 tags.add(new TaggedText(TagEnum.name, infraGenericPart));
570 }
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 (StringUtils.isNotBlank(speciesEpi)){
588 tags.add(new TaggedText(TagEnum.name, speciesEpi));
589 }
590
591 String infraSpeciesEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
592 if (StringUtils.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 (StringUtils.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 (StringUtils.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 (StringUtils.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 (StringUtils.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 (StringUtils.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 = StringUtils.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 (StringUtils.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 (StringUtils.isNotEmpty(appendedPhrase)){
842 tags.add(new TaggedText(TagEnum.name, appendedPhrase));
843 }
844 }
845
846
847 @Override
848 public String getLastEpithet(TaxonName taxonName) {
849 Rank rank = taxonName.getRank();
850 if(rank.isGenus() || rank.isSupraGeneric()) {
851 return taxonName.getGenusOrUninomial();
852 } else if(rank.isInfraGeneric()) {
853 return taxonName.getInfraGenericEpithet();
854 } else if(rank.isSpecies()) {
855 return taxonName.getSpecificEpithet();
856 } else {
857 return taxonName.getInfraSpecificEpithet();
858 }
859 }
860
861
862 /**
863 * {@inheritDoc}
864 */
865 @Override
866 protected List<TaggedText> doGetTaggedTitle(TaxonName taxonName) {
867 List<TaggedText> tags = new ArrayList<>();
868 if (taxonName.getNameType().isViral()){
869 String acronym = taxonName.getAcronym();
870 tags.add(new TaggedText(TagEnum.name, acronym));
871 return tags;
872 }else if (taxonName.isHybridFormula()){
873 //hybrid formula
874 String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
875 boolean isFirst = true;
876 List<HybridRelationship> rels = taxonName.getOrderedChildRelationships();
877 for (HybridRelationship rel: rels){
878 if (! isFirst){
879 tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
880 }
881 isFirst = false;
882 tags.addAll(getTaggedTitle(rel.getParentName()));
883 }
884 return tags;
885 }else if (taxonName.isAutonym()){
886 //Autonym
887 tags.addAll(handleTaggedAutonym(taxonName));
888 }else{ //not Autonym
889 List<TaggedText> nameTags = getTaggedName(taxonName);
890 tags.addAll(nameTags);
891 String authorCache = getAuthorshipCache(taxonName);
892 if (StringUtils.isNotBlank(authorCache)){
893 tags.add(new TaggedText(TagEnum.authors, authorCache));
894 }
895 }
896 return tags;
897 }
898
899 }