2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.cdm
.strategy
.cache
.name
;
11 import java
.util
.ArrayList
;
12 import java
.util
.List
;
13 import java
.util
.UUID
;
14 import java
.util
.regex
.Matcher
;
15 import java
.util
.regex
.Pattern
;
17 import org
.apache
.log4j
.Logger
;
19 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
20 import eu
.etaxonomy
.cdm
.common
.UTF8
;
21 import eu
.etaxonomy
.cdm
.model
.agent
.INomenclaturalAuthor
;
22 import eu
.etaxonomy
.cdm
.model
.name
.HybridRelationship
;
23 import eu
.etaxonomy
.cdm
.model
.name
.INonViralName
;
24 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
25 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
26 import eu
.etaxonomy
.cdm
.strategy
.cache
.TagEnum
;
27 import eu
.etaxonomy
.cdm
.strategy
.cache
.TaggedText
;
28 import eu
.etaxonomy
.cdm
.strategy
.cache
.TaggedTextBuilder
;
29 import eu
.etaxonomy
.cdm
.strategy
.exceptions
.UnknownCdmTypeException
;
30 import eu
.etaxonomy
.cdm
.strategy
.parser
.NonViralNameParserImplRegExBase
;
34 * This class is a default implementation for the INonViralNameCacheStrategy<T extends NonViralName>
36 * The method implements a cache strategy for botanical names so no method has to be overwritten by
37 * a subclass for botanic names.
38 * Where differing from this default botanic name strategy other subclasses should overwrite the
39 * existing methods, e.g. a CacheStrategy for zoological names should overwrite getAuthorAndExAuthor
42 public class TaxonNameDefaultCacheStrategy
43 extends NameCacheStrategyBase
44 implements INonViralNameCacheStrategy
{
46 private static final Logger logger
= Logger
.getLogger(TaxonNameDefaultCacheStrategy
.class);
47 private static final long serialVersionUID
= -6577757501563212669L;
49 final static UUID uuid
= UUID
.fromString("1cdda0d1-d5bc-480f-bf08-40a510a2f223");
51 protected String nameAuthorSeperator
= " ";
52 protected String basionymStart
= "(";
53 protected String basionymEnd
= ")";
54 protected String exAuthorSeperator
= " ex ";
55 private static String NOTHO
= "notho";
56 protected CharSequence basionymAuthorCombinationAuthorSeperator
= " ";
58 protected String zooAuthorYearSeperator
= ", ";
60 private String cultivarStart
= "'";
61 private String cultivarEnd
= "'";
64 public UUID
getUuid(){
68 // ************************** FACTORY ******************************/
70 public static TaxonNameDefaultCacheStrategy
NewInstance(){
71 return new TaxonNameDefaultCacheStrategy();
75 // ************ CONSTRUCTOR *******************/
77 protected TaxonNameDefaultCacheStrategy(){
81 /* **************** GETTER / SETTER **************************************/
84 * String that separates the NameCache part from the AuthorCache part
87 public String
getNameAuthorSeperator() {
88 return nameAuthorSeperator
;
90 public void setNameAuthorSeperator(String nameAuthorSeperator
) {
91 this.nameAuthorSeperator
= nameAuthorSeperator
;
96 * String the basionym author part starts with e.g. '('.
97 * This should correspond with the {@link TaxonNameDefaultCacheStrategy#getBasionymEnd() basionymEnd} attribute
100 public String
getBasionymStart() {
101 return basionymStart
;
103 public void setBasionymStart(String basionymStart
) {
104 this.basionymStart
= basionymStart
;
108 * String the basionym author part ends with e.g. ')'.
109 * This should correspond with the {@link TaxonNameDefaultCacheStrategy#getBasionymStart() basionymStart} attribute
112 public String
getBasionymEnd() {
115 public void setBasionymEnd(String basionymEnd
) {
116 this.basionymEnd
= basionymEnd
;
121 * String to separate ex author from author.
124 public String
getExAuthorSeperator() {
125 return exAuthorSeperator
;
127 public void setExAuthorSeperator(String exAuthorSeperator
) {
128 this.exAuthorSeperator
= exAuthorSeperator
;
133 * String that separates the basionym/original_combination author part from the combination author part
136 public CharSequence
getBasionymAuthorCombinationAuthorSeperator() {
137 return basionymAuthorCombinationAuthorSeperator
;
141 public void setBasionymAuthorCombinationAuthorSeperator( CharSequence basionymAuthorCombinationAuthorSeperator
) {
142 this.basionymAuthorCombinationAuthorSeperator
= basionymAuthorCombinationAuthorSeperator
;
145 public String
getZooAuthorYearSeperator() {
146 return zooAuthorYearSeperator
;
148 public void setZooAuthorYearSeperator(String authorYearSeperator
) {
149 this.zooAuthorYearSeperator
= authorYearSeperator
;
152 // ******************* Authorship ******************************/
155 public String
getAuthorshipCache(TaxonName taxonName
) {
156 if (taxonName
== null){
158 }else if (taxonName
.isViral()){
160 }else if(taxonName
.isProtectedAuthorshipCache() == true) {
162 return taxonName
.getAuthorshipCache();
164 return getNonCacheAuthorshipCache(taxonName
);
169 * Returns the authorshipcache string for the atomized authorship fields. Does not use the authorship field.
170 * @throws NullPointerException if nonViralName is null.
174 protected String
getNonCacheAuthorshipCache(TaxonName nonViralName
){
175 if (nonViralName
.getNameType().isZoological()){
176 return this.getZoologicalNonCacheAuthorshipCache(nonViralName
);
179 INomenclaturalAuthor combinationAuthor
= nonViralName
.getCombinationAuthorship();
180 INomenclaturalAuthor exCombinationAuthor
= nonViralName
.getExCombinationAuthorship();
181 INomenclaturalAuthor basionymAuthor
= nonViralName
.getBasionymAuthorship();
182 INomenclaturalAuthor exBasionymAuthor
= nonViralName
.getExBasionymAuthorship();
183 if (isCultivar(nonViralName
) ){
184 exCombinationAuthor
= null;
185 basionymAuthor
= null;
186 exBasionymAuthor
= null;
189 String basionymPart
= "";
190 String authorPart
= "";
192 if (basionymAuthor
!= null || exBasionymAuthor
!= null){
193 basionymPart
= basionymStart
+ getAuthorAndExAuthor(basionymAuthor
, exBasionymAuthor
) + basionymEnd
;
195 if (combinationAuthor
!= null || exCombinationAuthor
!= null){
196 authorPart
= getAuthorAndExAuthor(combinationAuthor
, exCombinationAuthor
);
198 result
= CdmUtils
.concat(basionymAuthorCombinationAuthorSeperator
, basionymPart
, authorPart
);
199 // if ("".equals(result)){
206 private boolean isCultivar(TaxonName name
) {
207 return name
.isCultivar() || isNotBlank(name
.getCultivarEpithet()) || isNotBlank(name
.getCultivarGroupEpithet());
210 protected String
getZoologicalNonCacheAuthorshipCache(TaxonName nonViralName
) {
211 if (nonViralName
== null){
215 INomenclaturalAuthor combinationAuthor
= nonViralName
.getCombinationAuthorship();
216 INomenclaturalAuthor exCombinationAuthor
= nonViralName
.getExCombinationAuthorship();
217 INomenclaturalAuthor basionymAuthor
= nonViralName
.getBasionymAuthorship();
218 INomenclaturalAuthor exBasionymAuthor
= nonViralName
.getExBasionymAuthorship();
219 Integer publicationYear
= nonViralName
.getPublicationYear();
220 Integer originalPublicationYear
= nonViralName
.getOriginalPublicationYear();
222 String basionymPart
= "";
223 String authorPart
= "";
225 if (basionymAuthor
!= null || exBasionymAuthor
!= null || originalPublicationYear
!= null ){
226 String authorAndEx
= getAuthorAndExAuthor(basionymAuthor
, exBasionymAuthor
);
227 String originalPublicationYearString
= originalPublicationYear
== null ?
null : String
.valueOf(originalPublicationYear
);
228 String authorAndExAndYear
= CdmUtils
.concat(zooAuthorYearSeperator
, authorAndEx
, originalPublicationYearString
);
229 basionymPart
= basionymStart
+ authorAndExAndYear
+ basionymEnd
;
231 if (combinationAuthor
!= null || exCombinationAuthor
!= null){
232 String authorAndEx
= getAuthorAndExAuthor(combinationAuthor
, exCombinationAuthor
);
233 String publicationYearString
= publicationYear
== null ?
null : String
.valueOf(publicationYear
);
234 authorPart
= CdmUtils
.concat(zooAuthorYearSeperator
, authorAndEx
, publicationYearString
);
236 result
= CdmUtils
.concat(basionymAuthorCombinationAuthorSeperator
, basionymPart
, authorPart
);
245 * Returns the AuthorCache part for a combination of an author and an ex author. This applies on
246 * combination authors as well as on basionym/orginal combination authors.
247 * The correct order is exAuthor ex author though some botanist do not know about and do it the
248 * other way round. (see 46.4-46.6 ICBN (Vienna Code, 2006))
250 protected String
getAuthorAndExAuthor(INomenclaturalAuthor author
, INomenclaturalAuthor exAuthor
){
251 String authorString
= "";
252 String exAuthorString
= "";
254 authorString
= getNomAuthorTitle(author
);
256 if (exAuthor
!= null){
257 exAuthorString
= getNomAuthorTitle(exAuthor
);
258 exAuthorString
+= exAuthorSeperator
;
260 String result
= exAuthorString
+ authorString
;
264 private String
getNomAuthorTitle(INomenclaturalAuthor author
) {
265 return CdmUtils
.Nz(author
.getNomenclaturalTitleCache());
269 * Checks if the given name should include the author in it's cached version.<BR>
270 * This is usually the case but not for <i>species aggregates</i>.
271 * @param nonViralName
274 protected boolean nameIncludesAuthorship(INonViralName nonViralName
){
275 Rank rank
= nonViralName
.getRank();
276 if (rank
!= null && rank
.isSpeciesAggregate()){
283 // ************* TAGGED NAME ***************************************/
286 protected List
<TaggedText
> doGetTaggedTitle(TaxonName taxonName
) {
287 List
<TaggedText
> tags
= new ArrayList
<>();
288 if (taxonName
.getNameType().isViral()){
289 String acronym
= taxonName
.getAcronym();
290 if (isNotBlank(taxonName
.getAcronym())){
291 //this is not according to the code
292 tags
.add(new TaggedText(TagEnum
.name
, acronym
));
295 }else if (taxonName
.isHybridFormula()){
297 String hybridSeparator
= NonViralNameParserImplRegExBase
.hybridSign
;
298 boolean isFirst
= true;
299 List
<HybridRelationship
> rels
= taxonName
.getOrderedChildRelationships();
300 for (HybridRelationship rel
: rels
){
302 tags
.add(new TaggedText(TagEnum
.hybridSign
, hybridSeparator
));
305 tags
.addAll(getTaggedTitle(rel
.getParentName()));
308 }else if (taxonName
.isAutonym()){
310 tags
.addAll(handleTaggedAutonym(taxonName
, true));
312 List
<TaggedText
> nameTags
= getTaggedName(taxonName
);
313 tags
.addAll(nameTags
);
314 String authorCache
= getAuthorshipCache(taxonName
);
315 if (isNotBlank(authorCache
)){
316 tags
.add(new TaggedText(TagEnum
.authors
, authorCache
));
323 * Returns the tag list of the name part (without author and reference).
328 public List
<TaggedText
> getTaggedName(TaxonName taxonName
) {
329 if (taxonName
== null){
331 }else if (taxonName
.isViral()){
334 List
<TaggedText
> tags
= new ArrayList
<>();
335 Rank rank
= taxonName
.getRank();
337 if (taxonName
.isProtectedNameCache()){
338 tags
.add(new TaggedText(TagEnum
.name
, taxonName
.getNameCache()));
339 }else if (taxonName
.isHybridFormula()){
341 //TODO graft-chimera (see also cultivars)
342 String hybridSeparator
= NonViralNameParserImplRegExBase
.hybridSign
;
343 boolean isFirst
= true;
344 List
<HybridRelationship
> rels
= taxonName
.getOrderedChildRelationships();
345 for (HybridRelationship rel
: rels
){
347 tags
.add(new TaggedText(TagEnum
.hybridSign
, hybridSeparator
));
350 tags
.addAll(getTaggedName(rel
.getParentName()));
354 }else if (rank
== null){
355 tags
= getRanklessTaggedNameCache(taxonName
, true);
356 }else if (rank
.isCultivar()){
357 tags
= getCultivarTaggedNameCache(taxonName
);
358 }else if (rank
.isInfraSpecific()){
359 tags
= getInfraSpeciesTaggedNameCache(taxonName
);
360 }else if (rank
.isSpecies() || isAggregateWithAuthorship(taxonName
, rank
) ){ //exception see #4288
361 tags
= getSpeciesTaggedNameCache(taxonName
, true);
362 }else if (rank
.isInfraGeneric()){
363 tags
= getInfraGenusTaggedNameCache(taxonName
, true);
364 }else if (rank
.isGenus()){
365 tags
= getGenusOrUninomialTaggedNameCache(taxonName
, true);
366 }else if (rank
.isSupraGeneric()){
367 tags
= getGenusOrUninomialTaggedNameCache(taxonName
, true);
369 tags
= getRanklessTaggedNameCache(taxonName
, true);
370 logger
.warn("Rank does not belong to a rank class: " + rank
.getTitleCache() + ". Used rankless nameCache for name " + taxonName
.getUuid());
372 //TODO handle appended phrase here instead of different places, check first if this is true for all
378 //***************************** PRIVATES ***************************************/
380 private List
<TaggedText
> getCultivarTaggedNameCache(TaxonName taxonName
) {
381 List
<TaggedText
> scientificNameTags
;
382 TaggedTextBuilder builder
= TaggedTextBuilder
.NewInstance();
383 if (isNotBlank(taxonName
.getInfraSpecificEpithet())){
384 scientificNameTags
= getInfraSpeciesTaggedNameCache(taxonName
, false, false);
385 } else if (isNotBlank(taxonName
.getSpecificEpithet())){
386 scientificNameTags
= getSpeciesTaggedNameCache(taxonName
, false);
387 } else if (isNotBlank(taxonName
.getInfraGenericEpithet())){
388 scientificNameTags
= getInfraGenusTaggedNameCache(taxonName
, false);
389 } else /*if (isNotBlank(taxonName.getGenusOrUninomial())) */{
390 scientificNameTags
= getGenusOrUninomialTaggedNameCache(taxonName
, false);
393 UUID rankUuid
= taxonName
.getRank().getUuid();
394 boolean rankIsHandled
= true;
395 String cultivarStr
= null;
396 String groupStr
= taxonName
.getCultivarGroupEpithet();
397 if (rankUuid
.equals(Rank
.uuidCultivar
)){
398 cultivarStr
= surroundedCultivarEpithet(taxonName
.getCultivarEpithet());
399 if (isNotBlank(cultivarStr
) && isNotBlank(groupStr
)){
400 groupStr
= surroundGroupWithBracket(groupStr
);
402 cultivarStr
= CdmUtils
.concat(" ", groupStr
, cultivarStr
);
403 }else if (rankUuid
.equals(Rank
.uuidCultivarGroup
)){
404 cultivarStr
= CdmUtils
.concat(" ", groupStr
, checkHasGroupEpithet(groupStr
)?
null: "Group");
405 }else if (rankUuid
.equals(Rank
.uuidGrexICNCP
)){
406 cultivarStr
= CdmUtils
.concat(" ", groupStr
, checkHasGrexEpithet(groupStr
)?
null: "grex");
408 rankIsHandled
= false;
411 builder
.addAll(scientificNameTags
);
412 }else if (rankUuid
.equals(Rank
.uuidGraftChimaera
)){
413 //TODO not yet fully implemented
414 cultivarStr
= "+ " + CdmUtils
.concat(" ", taxonName
.getGenusOrUninomial(), surroundedCultivarEpithet(taxonName
.getCultivarEpithet()));
415 }else if (rankUuid
.equals(Rank
.uuidDenominationClass
)){
416 //TODO dummy implementation
417 cultivarStr
= CdmUtils
.concat(" ", taxonName
.getGenusOrUninomial(), surroundedCultivarEpithet(taxonName
.getCultivarEpithet()));
418 } else { //(!rankIsHandled)
419 throw new IllegalStateException("Unsupported rank " + taxonName
.getRank().getTitleCache() + " for cultivar.");
421 if (isNotBlank(cultivarStr
)){
422 builder
.add(TagEnum
.cultivar
, cultivarStr
);
425 List
<TaggedText
> tags
= builder
.getTaggedText();
426 addAppendedTaggedPhrase(tags
, taxonName
, true);
430 private String
surroundGroupWithBracket(String groupStr
) {
431 if (groupStr
.matches(NonViralNameParserImplRegExBase
.grex
+ "$")){
433 }else if (groupStr
.matches(".*" + NonViralNameParserImplRegExBase
.grex
+ ".+")){
434 Matcher matcher
= Pattern
.compile(NonViralNameParserImplRegExBase
.grex
+ "\\s*" ).matcher(groupStr
);
436 return groupStr
.substring(0, matcher
.end()) + "("+ groupStr
.substring(matcher
.end())+ ")";
438 return "("+ groupStr
+ ")";
442 private boolean checkHasGroupEpithet(String group
) {
444 String
[] splits
= group
== null?
new String
[0]: group
.split("\\s+");
445 if (splits
.length
<= 1){
447 }else if (splits
[0].matches(NonViralNameParserImplRegExBase
.group
)
448 || splits
[splits
.length
-1].matches(NonViralNameParserImplRegExBase
.group
)){
455 private boolean checkHasGrexEpithet(String grex
) {
456 String
[] splits
= grex
== null?
new String
[0]: grex
.split("\\s+");
457 if (splits
.length
<= 1){
459 }else if (splits
[splits
.length
-1].matches(NonViralNameParserImplRegExBase
.grex
)){
466 private String
surroundedCultivarEpithet(String cultivarEpi
) {
467 return cultivarStart
+ CdmUtils
.Nz(cultivarEpi
) + cultivarEnd
;
470 private boolean isAggregateWithAuthorship(TaxonName nonViralName
, Rank rank
) {
474 return rank
.isSpeciesAggregate() && ( isNotBlank(nonViralName
.getAuthorshipCache()) || nonViralName
.getNomenclaturalReference() != null );
479 * Returns the tag list for an autonym taxon.
481 * @see NonViralName#isAutonym()
482 * @see BotanicalName#isAutonym()
483 * @param nonViralName
486 private List
<TaggedText
> handleTaggedAutonym(TaxonName nonViralName
, boolean addAppended
) {
487 List
<TaggedText
> tags
= null;
488 if (nonViralName
.isInfraSpecific()){
490 tags
= getSpeciesTaggedNameCache(nonViralName
, addAppended
);
493 String authorCache
= getAuthorshipCache(nonViralName
);
494 if (isNotBlank(authorCache
)){
495 tags
.add(new TaggedText(TagEnum
.authors
, authorCache
));
498 //infra species marker
499 if (nonViralName
.getRank() == null || !nonViralName
.getRank().isInfraSpecific()){
500 //TODO handle exception
501 logger
.warn("Rank for autonym does not exist or is not lower than species !!");
503 String infraSpeciesMarker
= nonViralName
.getRank().getAbbreviation();
504 if (nonViralName
.isTrinomHybrid()){
505 infraSpeciesMarker
= CdmUtils
.concat("", NOTHO
, infraSpeciesMarker
);
507 if (isNotBlank(infraSpeciesMarker
)){
508 tags
.add(new TaggedText(TagEnum
.rank
, infraSpeciesMarker
));
513 String infraSpeciesPart
= CdmUtils
.Nz(nonViralName
.getInfraSpecificEpithet()).trim();
514 if (isNotBlank(infraSpeciesPart
)){
515 tags
.add(new TaggedText(TagEnum
.name
, infraSpeciesPart
));
518 } else if (nonViralName
.isInfraGeneric()){
520 tags
=getGenusOrUninomialTaggedNameCache(nonViralName
, addAppended
);
523 String authorCache
= getAuthorshipCache(nonViralName
);
524 if (isNotBlank(authorCache
)){
525 tags
.add(new TaggedText(TagEnum
.authors
, authorCache
));
528 //infra species marker
529 if (nonViralName
.getRank() == null || !nonViralName
.getRank().isInfraGeneric()){
530 //TODO handle exception
531 logger
.warn("Rank for autonym does not exist or is not lower than species !!");
533 Rank rank
= nonViralName
.getRank();
534 String infraGenericMarker
= rank
.getAbbreviation();
535 if (rank
.equals(Rank
.SECTION_BOTANY()) || rank
.equals(Rank
.SUBSECTION_BOTANY())){
536 infraGenericMarker
= infraGenericMarker
.replace("(bot.)", "");
538 if (isNotBlank(infraGenericMarker
)){
539 tags
.add(new TaggedText(TagEnum
.rank
, infraGenericMarker
));
544 String infraGenericPart
= CdmUtils
.Nz(nonViralName
.getInfraGenericEpithet()).trim();
545 if (isNotBlank(infraGenericPart
)){
546 tags
.add(new TaggedText(TagEnum
.name
, infraGenericPart
));
556 * Returns the tag list for rankless taxa.
557 * @param nonViralName
560 protected List
<TaggedText
> getRanklessTaggedNameCache(INonViralName nonViralName
, boolean addAppended
){
561 List
<TaggedText
> tags
= getUninomialTaggedPart(nonViralName
);
562 String speciesEpi
= CdmUtils
.Nz(nonViralName
.getSpecificEpithet()).trim();
563 if (isNotBlank(speciesEpi
)){
564 tags
.add(new TaggedText(TagEnum
.name
, speciesEpi
));
567 String infraSpeciesEpi
= CdmUtils
.Nz(nonViralName
.getInfraSpecificEpithet());
568 if (isNotBlank(infraSpeciesEpi
)){
569 tags
.add(new TaggedText(TagEnum
.name
, infraSpeciesEpi
));
572 //result += " (rankless)";
573 addAppendedTaggedPhrase(tags
, nonViralName
, addAppended
);
578 * Returns the tag list for the first epithet (including a hybrid sign if required).
579 * @param nonViralName
582 private List
<TaggedText
> getUninomialTaggedPart(INonViralName nonViralName
) {
583 List
<TaggedText
> tags
= new ArrayList
<>();
585 if (nonViralName
.isMonomHybrid()){
586 addHybridPrefix(tags
);
589 String uninomial
= CdmUtils
.Nz(nonViralName
.getGenusOrUninomial()).trim();
590 if (isNotBlank(uninomial
)){
591 tags
.add(new TaggedText(TagEnum
.name
, uninomial
));
598 * Returns the tag list for an genus or higher taxon.
600 * @param nonViralName
603 protected List
<TaggedText
> getGenusOrUninomialTaggedNameCache(INonViralName nonViralName
, boolean addAppended
){
604 List
<TaggedText
> tags
= getUninomialTaggedPart(nonViralName
);
605 addAppendedTaggedPhrase(tags
, nonViralName
, addAppended
);
610 * Returns the tag list for an infrageneric taxon (including species aggregates).
612 * @see #getSpeciesAggregateTaggedCache(NonViralName)
613 * @param nonViralName
616 protected List
<TaggedText
> getInfraGenusTaggedNameCache(INonViralName nonViralName
, boolean addAppended
){
617 Rank rank
= nonViralName
.getRank();
618 if (rank
!= null && rank
.isSpeciesAggregate() && isBlank(nonViralName
.getAuthorshipCache())){
619 return getSpeciesAggregateTaggedCache(nonViralName
, addAppended
);
623 List
<TaggedText
> tags
= getUninomialTaggedPart(nonViralName
);
626 String infraGenericMarker
;
629 infraGenericMarker
= rank
.getInfraGenericMarker();
630 if (rank
.equals(Rank
.SECTION_BOTANY()) || rank
.equals(Rank
.SUBSECTION_BOTANY())){
631 infraGenericMarker
= infraGenericMarker
.replace("(bot.)", "");
633 } catch (UnknownCdmTypeException e
) {
634 infraGenericMarker
= "'unhandled infrageneric rank'";
637 infraGenericMarker
= "'undefined infrageneric rank'";
639 String infraGenEpi
= CdmUtils
.Nz(nonViralName
.getInfraGenericEpithet()).trim();
640 if (nonViralName
.isBinomHybrid()){
641 infraGenericMarker
= CdmUtils
.concat("", NOTHO
, infraGenericMarker
);
644 addInfraGenericPart(nonViralName
, tags
, infraGenericMarker
, infraGenEpi
);
646 addAppendedTaggedPhrase(tags
, nonViralName
, addAppended
);
652 * Default implementation for the infrageneric part of a name.
653 * This is usually the infrageneric marker and the infrageneric epitheton. But may be
654 * implemented differently e.g. for zoological names the infrageneric epitheton
655 * may be surrounded by brackets and the marker left out.
656 * @param nonViralName
658 * @param infraGenericMarker
660 protected void addInfraGenericPart(
661 @SuppressWarnings("unused") INonViralName name
,
662 List
<TaggedText
> tags
,
663 String infraGenericMarker
,
664 String infraGenEpi
) {
666 tags
.add(new TaggedText(TagEnum
.rank
, infraGenericMarker
));
669 if (isNotBlank(infraGenEpi
)){
670 tags
.add(new TaggedText(TagEnum
.name
, infraGenEpi
));
675 * Returns the tag list for a species aggregate (or similar) taxon.<BR>
676 * Possible ranks for a <i>species aggregate</i> are "aggr.", "species group", ...
677 * @param nonViralName
680 protected List
<TaggedText
> getSpeciesAggregateTaggedCache(INonViralName nonViralName
, boolean addAppended
){
681 if (! isBlank(nonViralName
.getAuthorshipCache())){
682 List
<TaggedText
> result
= getSpeciesTaggedNameCache(nonViralName
, addAppended
);
686 List
<TaggedText
> tags
= getGenusAndSpeciesTaggedPart(nonViralName
);
688 addSpeciesAggregateTaggedEpithet(tags
, nonViralName
);
689 addAppendedTaggedPhrase(tags
, nonViralName
, addAppended
);
694 * Adds the aggregate tag to the tag list.
696 private void addSpeciesAggregateTaggedEpithet(List
<TaggedText
> tags
, INonViralName nonViralName
) {
699 marker
= nonViralName
.getRank().getInfraGenericMarker();
700 } catch (UnknownCdmTypeException e
) {
701 marker
= "'unknown aggregat type'";
703 if (isNotBlank(marker
)){
704 tags
.add(new TaggedText(TagEnum
.rank
, marker
));
709 * Returns the tag list for a species taxon.
711 protected List
<TaggedText
> getSpeciesTaggedNameCache(INonViralName nonViralName
, boolean addAppended
){
712 List
<TaggedText
> tags
= getGenusAndSpeciesTaggedPart(nonViralName
);
713 addAppendedTaggedPhrase(tags
, nonViralName
, addAppended
);
717 protected List
<TaggedText
> getInfraSpeciesTaggedNameCache(TaxonName name
){
718 if (name
.getNameType().isZoological()){
719 boolean includeMarker
= includeInfraSpecificMarkerForZooNames(name
);
720 return getInfraSpeciesTaggedNameCache(name
, includeMarker
, true);
722 return getInfraSpeciesTaggedNameCache(name
, true, true);
726 protected boolean includeInfraSpecificMarkerForZooNames(TaxonName name
){
727 return ! (name
.isAutonym()); //only exclude marker if autonym, see also ZooNameNoMarkerCacheStrategy
731 * Creates the tag list for an infraspecific taxon. If include is true the result will contain
732 * the infraspecific marker (e.g. "var.")
733 * @param nonViralName
734 * @param includeMarker
737 protected List
<TaggedText
> getInfraSpeciesTaggedNameCache(INonViralName nonViralName
,
738 boolean includeMarker
, boolean addAppended
){
739 List
<TaggedText
> tags
= getGenusAndSpeciesTaggedPart(nonViralName
);
740 if (includeMarker
|| nonViralName
.isTrinomHybrid()){
741 String marker
= (nonViralName
.getRank().getAbbreviation()).trim().replace("null", "");
742 if (nonViralName
.isTrinomHybrid()){
743 marker
= CdmUtils
.concat("", NOTHO
, marker
);
745 if (isNotBlank(marker
)){
746 tags
.add(new TaggedText(TagEnum
.rank
, marker
));
750 String infrSpecEpi
= CdmUtils
.Nz(nonViralName
.getInfraSpecificEpithet());
752 infrSpecEpi
= infrSpecEpi
.trim().replace("null", "");
754 if (isNotBlank(infrSpecEpi
)){
755 tags
.add(new TaggedText(TagEnum
.name
, infrSpecEpi
));
758 addAppendedTaggedPhrase(tags
, nonViralName
, addAppended
);
764 * Adds a tag for the hybrid sign and an empty separator to avoid trailing whitespaces.
767 private void addHybridPrefix(List
<TaggedText
> tags
) {
768 tags
.add(new TaggedText(TagEnum
.hybridSign
, NonViralNameParserImplRegExBase
.hybridSign
));
769 tags
.add(new TaggedText(TagEnum
.separator
, "")); //no whitespace separator
773 * Creates the tag list for the genus and species part.
774 * @param nonViralName
777 private List
<TaggedText
> getGenusAndSpeciesTaggedPart(INonViralName nonViralName
) {
779 List
<TaggedText
> tags
= getUninomialTaggedPart(nonViralName
);
782 boolean hasInfraGenericEpi
= isNotBlank(nonViralName
.getInfraGenericEpithet());
783 if (hasInfraGenericEpi
){
784 String infrGenEpi
= nonViralName
.getInfraGenericEpithet().trim();
785 if (nonViralName
.isBinomHybrid()){
786 //maybe not correct but not really expected to happen
787 infrGenEpi
= UTF8
.HYBRID
+ infrGenEpi
;
789 infrGenEpi
= "(" + infrGenEpi
+ ")";
790 tags
.add(new TaggedText(TagEnum
.name
, infrGenEpi
));
794 String specEpi
= CdmUtils
.Nz(nonViralName
.getSpecificEpithet()).trim();
795 if (! hasInfraGenericEpi
&& nonViralName
.isBinomHybrid() ||
796 hasInfraGenericEpi
&& nonViralName
.isTrinomHybrid()){
797 addHybridPrefix(tags
);
799 if (isNotBlank(specEpi
)){
800 tags
.add(new TaggedText(TagEnum
.name
, specEpi
));
806 * Adds the tag for the appended phrase if an appended phrase exists
808 * @param nonViralName
811 protected void addAppendedTaggedPhrase(List
<TaggedText
> tags
, INonViralName nonViralName
, boolean addAppended
){
815 String appendedPhrase
= nonViralName
==null ?
null : nonViralName
.getAppendedPhrase();
816 if (isNotBlank(appendedPhrase
)){
817 tags
.add(new TaggedText(TagEnum
.name
, appendedPhrase
));
822 public String
getLastEpithet(TaxonName taxonName
) {
823 Rank rank
= taxonName
.getRank();
824 if(rank
.isGenus() || rank
.isSupraGeneric()) {
825 return taxonName
.getGenusOrUninomial();
826 } else if(rank
.isInfraGeneric()) {
827 return taxonName
.getInfraGenericEpithet();
828 } else if(rank
.isSpecies()) {
829 return taxonName
.getSpecificEpithet();
831 return taxonName
.getInfraSpecificEpithet();