cleanup
[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.List;
13 import java.util.UUID;
14 import java.util.regex.Matcher;
15 import java.util.regex.Pattern;
16
17 import org.apache.log4j.Logger;
18
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;
31
32
33 /**
34 * This class is a default implementation for the INonViralNameCacheStrategy<T extends NonViralName>
35 * interface.<BR>
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
40 * @author a.mueller
41 */
42 public class TaxonNameDefaultCacheStrategy
43 extends NameCacheStrategyBase
44 implements INonViralNameCacheStrategy {
45
46 private static final Logger logger = Logger.getLogger(TaxonNameDefaultCacheStrategy.class);
47 private static final long serialVersionUID = -6577757501563212669L;
48
49 final static UUID uuid = UUID.fromString("1cdda0d1-d5bc-480f-bf08-40a510a2f223");
50
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 = " ";
57
58 protected String zooAuthorYearSeperator = ", ";
59
60 private String cultivarStart = "'";
61 private String cultivarEnd = "'";
62
63 @Override
64 public UUID getUuid(){
65 return uuid;
66 }
67
68 // ************************** FACTORY ******************************/
69
70 public static TaxonNameDefaultCacheStrategy NewInstance(){
71 return new TaxonNameDefaultCacheStrategy();
72 }
73
74
75 // ************ CONSTRUCTOR *******************/
76
77 protected TaxonNameDefaultCacheStrategy(){
78 super();
79 }
80
81 /* **************** GETTER / SETTER **************************************/
82
83 /**
84 * String that separates the NameCache part from the AuthorCache part
85 * @return
86 */
87 public String getNameAuthorSeperator() {
88 return nameAuthorSeperator;
89 }
90 public void setNameAuthorSeperator(String nameAuthorSeperator) {
91 this.nameAuthorSeperator = nameAuthorSeperator;
92 }
93
94
95 /**
96 * String the basionym author part starts with e.g. '('.
97 * This should correspond with the {@link TaxonNameDefaultCacheStrategy#getBasionymEnd() basionymEnd} attribute
98 * @return
99 */
100 public String getBasionymStart() {
101 return basionymStart;
102 }
103 public void setBasionymStart(String basionymStart) {
104 this.basionymStart = basionymStart;
105 }
106
107 /**
108 * String the basionym author part ends with e.g. ')'.
109 * This should correspond with the {@link TaxonNameDefaultCacheStrategy#getBasionymStart() basionymStart} attribute
110 * @return
111 */
112 public String getBasionymEnd() {
113 return basionymEnd;
114 }
115 public void setBasionymEnd(String basionymEnd) {
116 this.basionymEnd = basionymEnd;
117 }
118
119
120 /**
121 * String to separate ex author from author.
122 * @return
123 */
124 public String getExAuthorSeperator() {
125 return exAuthorSeperator;
126 }
127 public void setExAuthorSeperator(String exAuthorSeperator) {
128 this.exAuthorSeperator = exAuthorSeperator;
129 }
130
131
132 /**
133 * String that separates the basionym/original_combination author part from the combination author part
134 * @return
135 */
136 public CharSequence getBasionymAuthorCombinationAuthorSeperator() {
137 return basionymAuthorCombinationAuthorSeperator;
138 }
139
140
141 public void setBasionymAuthorCombinationAuthorSeperator( CharSequence basionymAuthorCombinationAuthorSeperator) {
142 this.basionymAuthorCombinationAuthorSeperator = basionymAuthorCombinationAuthorSeperator;
143 }
144
145 public String getZooAuthorYearSeperator() {
146 return zooAuthorYearSeperator;
147 }
148 public void setZooAuthorYearSeperator(String authorYearSeperator) {
149 this.zooAuthorYearSeperator = authorYearSeperator;
150 }
151
152 // ******************* Authorship ******************************/
153
154 @Override
155 public String getAuthorshipCache(TaxonName taxonName) {
156 if (taxonName == null){
157 return null;
158 }else if (taxonName.isViral()){
159 return null;
160 }else if(taxonName.isProtectedAuthorshipCache() == true) {
161 //cache protected
162 return taxonName.getAuthorshipCache();
163 }else{
164 return getNonCacheAuthorshipCache(taxonName);
165 }
166 }
167
168 /**
169 * Returns the authorshipcache string for the atomized authorship fields. Does not use the authorship field.
170 * @throws NullPointerException if nonViralName is null.
171 * @param taxonName
172 * @return
173 */
174 protected String getNonCacheAuthorshipCache(TaxonName nonViralName){
175 if (nonViralName.getNameType().isZoological()){
176 return this.getZoologicalNonCacheAuthorshipCache(nonViralName);
177 }else{
178 String result = "";
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;
187 }
188
189 String basionymPart = "";
190 String authorPart = "";
191 //basionym
192 if (basionymAuthor != null || exBasionymAuthor != null){
193 basionymPart = basionymStart + getAuthorAndExAuthor(basionymAuthor, exBasionymAuthor) + basionymEnd;
194 }
195 if (combinationAuthor != null || exCombinationAuthor != null){
196 authorPart = getAuthorAndExAuthor(combinationAuthor, exCombinationAuthor);
197 }
198 result = CdmUtils.concat(basionymAuthorCombinationAuthorSeperator, basionymPart, authorPart);
199 // if ("".equals(result)){
200 // result = null;
201 // }
202 return result;
203 }
204 }
205
206 private boolean isCultivar(TaxonName name) {
207 return name.isCultivar() || isNotBlank(name.getCultivarEpithet()) || isNotBlank(name.getCultivarGroupEpithet());
208 }
209
210 protected String getZoologicalNonCacheAuthorshipCache(TaxonName nonViralName) {
211 if (nonViralName == null){
212 return null;
213 }
214 String result = "";
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();
221
222 String basionymPart = "";
223 String authorPart = "";
224 //basionym
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;
230 }
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);
235 }
236 result = CdmUtils.concat(basionymAuthorCombinationAuthorSeperator, basionymPart, authorPart);
237 if (result == null){
238 result = "";
239 }
240 return result;
241 }
242
243
244 /**
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))
249 */
250 protected String getAuthorAndExAuthor(INomenclaturalAuthor author, INomenclaturalAuthor exAuthor){
251 String authorString = "";
252 String exAuthorString = "";
253 if (author != null){
254 authorString = getNomAuthorTitle(author);
255 }
256 if (exAuthor != null){
257 exAuthorString = getNomAuthorTitle(exAuthor);
258 exAuthorString += exAuthorSeperator;
259 }
260 String result = exAuthorString + authorString;
261 return result;
262 }
263
264 private String getNomAuthorTitle(INomenclaturalAuthor author) {
265 return CdmUtils.Nz(author.getNomenclaturalTitleCache());
266 }
267
268 /**
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
272 * @return
273 */
274 protected boolean nameIncludesAuthorship(INonViralName nonViralName){
275 Rank rank = nonViralName.getRank();
276 if (rank != null && rank.isSpeciesAggregate()){
277 return false;
278 }else{
279 return true;
280 }
281 }
282
283 // ************* TAGGED NAME ***************************************/
284
285 @Override
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));
293 }
294 return tags;
295 }else if (taxonName.isHybridFormula()){
296 //hybrid formula
297 String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
298 boolean isFirst = true;
299 List<HybridRelationship> rels = taxonName.getOrderedChildRelationships();
300 for (HybridRelationship rel: rels){
301 if (! isFirst){
302 tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
303 }
304 isFirst = false;
305 tags.addAll(getTaggedTitle(rel.getParentName()));
306 }
307 return tags;
308 }else if (taxonName.isAutonym()){
309 //Autonym
310 tags.addAll(handleTaggedAutonym(taxonName));
311 }else{ //not Autonym
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));
317 }
318 }
319 return tags;
320 }
321
322 /**
323 * Returns the tag list of the name part (without author and reference).
324 * @param taxonName
325 * @return
326 */
327 @Override
328 public List<TaggedText> getTaggedName(TaxonName taxonName) {
329 if (taxonName == null){
330 return null;
331 }else if (taxonName.isViral()){
332 return null;
333 }
334 List<TaggedText> tags = new ArrayList<>();
335 Rank rank = taxonName.getRank();
336
337 if (taxonName.isProtectedNameCache()){
338 tags.add(new TaggedText(TagEnum.name, taxonName.getNameCache()));
339 }else if (taxonName.isHybridFormula()){
340 //hybrid formula
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){
346 if (! isFirst){
347 tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
348 }
349 isFirst = false;
350 tags.addAll(getTaggedName(rel.getParentName()));
351 }
352 return tags;
353
354 }else if (rank == null){
355 tags = getRanklessTaggedNameCache(taxonName);
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);
362 }else if (rank.isInfraGeneric()){
363 tags = getInfraGenusTaggedNameCache(taxonName);
364 }else if (rank.isGenus()){
365 tags = getGenusOrUninomialTaggedNameCache(taxonName);
366 }else if (rank.isSupraGeneric()){
367 tags = getGenusOrUninomialTaggedNameCache(taxonName);
368 }else{
369 tags = getRanklessTaggedNameCache(taxonName);
370 logger.warn("Rank does not belong to a rank class: " + rank.getTitleCache() + ". Used rankless nameCache for name " + taxonName.getUuid());
371 }
372 //TODO handle appended phrase here instead of different places, check first if this is true for all
373 //cases
374
375 return tags;
376 }
377
378 //***************************** PRIVATES ***************************************/
379
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);
385 } else if (isNotBlank(taxonName.getSpecificEpithet())){
386 scientificNameTags = getSpeciesTaggedNameCache(taxonName);
387 } else if (isNotBlank(taxonName.getInfraGenericEpithet())){
388 scientificNameTags = getInfraGenusTaggedNameCache(taxonName);
389 } else /*if (isNotBlank(taxonName.getGenusOrUninomial())) */{
390 scientificNameTags = getGenusOrUninomialTaggedNameCache(taxonName);
391 }
392
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);
399 if (isNotBlank(cultivarStr) && isNotBlank(groupStr)){
400 groupStr = surroundGroupWithBracket(groupStr);
401 }
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.uuidGrex)){
406 cultivarStr = CdmUtils.concat(" ", groupStr, checkHasGrexEpithet(groupStr)? null: "grex");
407 }else{
408 rankIsHandled = false;
409 }
410 if (isNotBlank(cultivarStr)){
411 builder.addAll(scientificNameTags);
412 builder.add(TagEnum.cultivar, cultivarStr);
413 }
414
415 if (rankUuid.equals(Rank.uuidGraftChimaera)){
416 //TODO not yet fully implemented
417 cultivarStr = "+ " + taxonName.getGenusOrUninomial() + " " + surroundedCultivarEpithet(taxonName);
418 builder.add(TagEnum.cultivar, cultivarStr);
419 } else if (!rankIsHandled){
420 throw new IllegalStateException("Unsupported rank " + taxonName.getRank().getTitleCache() + " for cultivar.");
421 }
422
423 return builder.getTaggedText();
424 }
425
426 private String surroundGroupWithBracket(String groupStr) {
427 if (groupStr.matches(NonViralNameParserImplRegExBase.grex + "$")){
428 return groupStr;
429 }else if (groupStr.matches(".*" + NonViralNameParserImplRegExBase.grex + ".+")){
430 Matcher matcher = Pattern.compile(NonViralNameParserImplRegExBase.grex + "\\s*" ).matcher(groupStr);
431 matcher.find();
432 return groupStr.substring(0, matcher.end()) + "("+ groupStr.substring(matcher.end())+ ")";
433 }else{
434 return "("+ groupStr + ")";
435 }
436 }
437
438 private boolean checkHasGroupEpithet(String group) {
439
440 String[] splits = group == null? new String[0]: group.split("\\s+");
441 if (splits.length <= 1){
442 return false;
443 }else if (splits[0].matches(NonViralNameParserImplRegExBase.group)
444 || splits[splits.length-1].matches(NonViralNameParserImplRegExBase.group)){
445 return true;
446 }else{
447 return false;
448 }
449 }
450
451 private boolean checkHasGrexEpithet(String grex) {
452 String[] splits = grex == null? new String[0]: grex.split("\\s+");
453 if (splits.length <= 1){
454 return false;
455 }else if (splits[splits.length-1].matches(NonViralNameParserImplRegExBase.grex)){
456 return true;
457 }else{
458 return false;
459 }
460 }
461
462 private String surroundedCultivarEpithet(TaxonName taxonName) {
463 return cultivarStart + taxonName.getCultivarEpithet() + cultivarEnd;
464 }
465
466 private boolean isAggregateWithAuthorship(TaxonName nonViralName, Rank rank) {
467 if (rank == null){
468 return false;
469 }else{
470 return rank.isSpeciesAggregate() && ( isNotBlank(nonViralName.getAuthorshipCache()) || nonViralName.getNomenclaturalReference() != null );
471 }
472 }
473
474 /**
475 * Returns the tag list for an autonym taxon.
476 *
477 * @see NonViralName#isAutonym()
478 * @see BotanicalName#isAutonym()
479 * @param nonViralName
480 * @return
481 */
482 private List<TaggedText> handleTaggedAutonym(TaxonName nonViralName) {
483 List<TaggedText> tags = null;
484 if (nonViralName.isInfraSpecific()){
485 //species part
486 tags = getSpeciesTaggedNameCache(nonViralName);
487
488 //author
489 String authorCache = getAuthorshipCache(nonViralName);
490 if (isNotBlank(authorCache)){
491 tags.add(new TaggedText(TagEnum.authors, authorCache));
492 }
493
494 //infra species marker
495 if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraSpecific()){
496 //TODO handle exception
497 logger.warn("Rank for autonym does not exist or is not lower than species !!");
498 }else{
499 String infraSpeciesMarker = nonViralName.getRank().getAbbreviation();
500 if (nonViralName.isTrinomHybrid()){
501 infraSpeciesMarker = CdmUtils.concat("", NOTHO, infraSpeciesMarker);
502 }
503 if (isNotBlank(infraSpeciesMarker)){
504 tags.add(new TaggedText(TagEnum.rank, infraSpeciesMarker));
505 }
506 }
507
508 //infra species
509 String infraSpeciesPart = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet()).trim();
510 if (isNotBlank(infraSpeciesPart)){
511 tags.add(new TaggedText(TagEnum.name, infraSpeciesPart));
512 }
513
514 } else if (nonViralName.isInfraGeneric()){
515 //genus part
516 tags =getGenusOrUninomialTaggedNameCache(nonViralName);
517
518 //author
519 String authorCache = getAuthorshipCache(nonViralName);
520 if (isNotBlank(authorCache)){
521 tags.add(new TaggedText(TagEnum.authors, authorCache));
522 }
523
524 //infra species marker
525 if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraGeneric()){
526 //TODO handle exception
527 logger.warn("Rank for autonym does not exist or is not lower than species !!");
528 }else{
529 Rank rank = nonViralName.getRank();
530 String infraGenericMarker = rank.getAbbreviation();
531 if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
532 infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
533 }
534 if (isNotBlank(infraGenericMarker)){
535 tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
536 }
537 }
538
539 //infra genus
540 String infraGenericPart = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
541 if (isNotBlank(infraGenericPart)){
542 tags.add(new TaggedText(TagEnum.name, infraGenericPart));
543 }
544 }
545
546 return tags;
547 }
548
549
550
551 /**
552 * Returns the tag list for rankless taxa.
553 * @param nonViralName
554 * @return
555 */
556 protected List<TaggedText> getRanklessTaggedNameCache(INonViralName nonViralName){
557 List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
558 String speciesEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
559 if (isNotBlank(speciesEpi)){
560 tags.add(new TaggedText(TagEnum.name, speciesEpi));
561 }
562
563 String infraSpeciesEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
564 if (isNotBlank(infraSpeciesEpi)){
565 tags.add(new TaggedText(TagEnum.name, infraSpeciesEpi));
566 }
567
568 //result += " (rankless)";
569 addAppendedTaggedPhrase(tags, nonViralName);
570 return tags;
571 }
572
573 /**
574 * Returns the tag list for the first epithet (including a hybrid sign if required).
575 * @param nonViralName
576 * @return
577 */
578 private List<TaggedText> getUninomialTaggedPart(INonViralName nonViralName) {
579 List<TaggedText> tags = new ArrayList<>();
580
581 if (nonViralName.isMonomHybrid()){
582 addHybridPrefix(tags);
583 }
584
585 String uninomial = CdmUtils.Nz(nonViralName.getGenusOrUninomial()).trim();
586 if (isNotBlank(uninomial)){
587 tags.add(new TaggedText(TagEnum.name, uninomial));
588 }
589
590 return tags;
591 }
592
593 /**
594 * Returns the tag list for an genus or higher taxon.
595 *
596 * @param nonViralName
597 * @return
598 */
599 protected List<TaggedText> getGenusOrUninomialTaggedNameCache(INonViralName nonViralName){
600 List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
601 addAppendedTaggedPhrase(tags, nonViralName);
602 return tags;
603 }
604
605 /**
606 * Returns the tag list for an infrageneric taxon (including species aggregates).
607 *
608 * @see #getSpeciesAggregateTaggedCache(NonViralName)
609 * @param nonViralName
610 * @return
611 */
612 protected List<TaggedText> getInfraGenusTaggedNameCache(INonViralName nonViralName){
613 Rank rank = nonViralName.getRank();
614 if (rank != null && rank.isSpeciesAggregate() && isBlank(nonViralName.getAuthorshipCache())){
615 return getSpeciesAggregateTaggedCache(nonViralName);
616 }
617
618 //genus
619 List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
620
621 //marker
622 String infraGenericMarker;
623 if (rank != null){
624 try {
625 infraGenericMarker = rank.getInfraGenericMarker();
626 if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
627 infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
628 }
629 } catch (UnknownCdmTypeException e) {
630 infraGenericMarker = "'unhandled infrageneric rank'";
631 }
632 }else{
633 infraGenericMarker = "'undefined infrageneric rank'";
634 }
635 String infraGenEpi = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
636 if (nonViralName.isBinomHybrid()){
637 infraGenericMarker = CdmUtils.concat("", NOTHO, infraGenericMarker);
638 }
639
640 addInfraGenericPart(nonViralName, tags, infraGenericMarker, infraGenEpi);
641
642 addAppendedTaggedPhrase(tags, nonViralName);
643 return tags;
644 }
645
646
647 /**
648 * Default implementation for the infrageneric part of a name.
649 * This is usually the infrageneric marker and the infrageneric epitheton. But may be
650 * implemented differently e.g. for zoological names the infrageneric epitheton
651 * may be surrounded by brackets and the marker left out.
652 * @param nonViralName
653 * @param tags
654 * @param infraGenericMarker
655 */
656 protected void addInfraGenericPart(
657 @SuppressWarnings("unused") INonViralName name,
658 List<TaggedText> tags,
659 String infraGenericMarker,
660 String infraGenEpi) {
661 //add marker
662 tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
663
664 //add epitheton
665 if (isNotBlank(infraGenEpi)){
666 tags.add(new TaggedText(TagEnum.name, infraGenEpi));
667 }
668 }
669
670 /**
671 * Returns the tag list for a species aggregate (or similar) taxon.<BR>
672 * Possible ranks for a <i>species aggregate</i> are "aggr.", "species group", ...
673 * @param nonViralName
674 * @return
675 */
676 protected List<TaggedText> getSpeciesAggregateTaggedCache(INonViralName nonViralName){
677 if (! isBlank(nonViralName.getAuthorshipCache())){
678 List<TaggedText> result = getSpeciesTaggedNameCache(nonViralName);
679 return result;
680 }
681
682
683 List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
684
685 addSpeciesAggregateTaggedEpithet(tags, nonViralName);
686 addAppendedTaggedPhrase(tags, nonViralName);
687 return tags;
688 }
689
690 /**
691 * Adds the aggregate tag to the tag list.
692 * @param tags
693 * @param nonViralName
694 */
695 private void addSpeciesAggregateTaggedEpithet(List<TaggedText> tags, INonViralName nonViralName) {
696 String marker;
697 try {
698 marker = nonViralName.getRank().getInfraGenericMarker();
699 } catch (UnknownCdmTypeException e) {
700 marker = "'unknown aggregat type'";
701 }
702 if (isNotBlank(marker)){
703 tags.add(new TaggedText(TagEnum.rank, marker));
704 }
705 }
706
707
708 /**
709 * Returns the tag list for a species taxon.
710 * @param nonViralName
711 * @return
712 */
713 protected List<TaggedText> getSpeciesTaggedNameCache(INonViralName nonViralName){
714 List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
715 addAppendedTaggedPhrase(tags, nonViralName);
716 return tags;
717 }
718
719 protected List<TaggedText> getInfraSpeciesTaggedNameCache(TaxonName name){
720 if (name.getNameType().isZoological()){
721 boolean includeMarker =includeInfraSpecificMarkerForZooNames(name);
722 return getInfraSpeciesTaggedNameCache(name, includeMarker);
723 }else{
724 return getInfraSpeciesTaggedNameCache(name, true);
725 }
726 }
727
728 protected boolean includeInfraSpecificMarkerForZooNames(TaxonName name){
729 return ! (name.isAutonym()); //only exclude marker if autonym, see also ZooNameNoMarkerCacheStrategy
730 }
731
732 /**
733 * Creates the tag list for an infraspecific taxon. If include is true the result will contain
734 * the infraspecific marker (e.g. "var.")
735 * @param nonViralName
736 * @param includeMarker
737 * @return
738 */
739 protected List<TaggedText> getInfraSpeciesTaggedNameCache(INonViralName nonViralName, boolean includeMarker){
740 List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
741 if (includeMarker || nonViralName.isTrinomHybrid()){
742 String marker = (nonViralName.getRank().getAbbreviation()).trim().replace("null", "");
743 if (nonViralName.isTrinomHybrid()){
744 marker = CdmUtils.concat("", NOTHO, marker);
745 }
746 if (isNotBlank(marker)){
747 tags.add(new TaggedText(TagEnum.rank, marker));
748 }
749
750 }
751 String infrSpecEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
752
753 infrSpecEpi = infrSpecEpi.trim().replace("null", "");
754
755 if (isNotBlank(infrSpecEpi)){
756 tags.add(new TaggedText(TagEnum.name, infrSpecEpi));
757 }
758
759 addAppendedTaggedPhrase(tags, nonViralName);
760 return tags;
761 }
762
763
764 /**
765 * Adds a tag for the hybrid sign and an empty separator to avoid trailing whitespaces.
766 * @param tags
767 */
768 private void addHybridPrefix(List<TaggedText> tags) {
769 tags.add(new TaggedText(TagEnum.hybridSign, NonViralNameParserImplRegExBase.hybridSign));
770 tags.add(new TaggedText(TagEnum.separator, "")); //no whitespace separator
771 }
772
773 /**
774 * Creates the tag list for the genus and species part.
775 * @param nonViralName
776 * @return
777 */
778 private List<TaggedText> getGenusAndSpeciesTaggedPart(INonViralName nonViralName) {
779 //Uninomial
780 List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
781
782 //InfraGenericEpi
783 boolean hasInfraGenericEpi = isNotBlank(nonViralName.getInfraGenericEpithet());
784 if (hasInfraGenericEpi){
785 String infrGenEpi = nonViralName.getInfraGenericEpithet().trim();
786 if (nonViralName.isBinomHybrid()){
787 //maybe not correct but not really expected to happen
788 infrGenEpi = UTF8.HYBRID + infrGenEpi;
789 }
790 infrGenEpi = "(" + infrGenEpi + ")";
791 tags.add(new TaggedText(TagEnum.name, infrGenEpi));
792 }
793
794 //Species Epi
795 String specEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
796 if (! hasInfraGenericEpi && nonViralName.isBinomHybrid() ||
797 hasInfraGenericEpi && nonViralName.isTrinomHybrid()){
798 addHybridPrefix(tags);
799 }
800 if (isNotBlank(specEpi)){
801 tags.add(new TaggedText(TagEnum.name, specEpi));
802 }
803 return tags;
804 }
805
806 /**
807 * Adds the tag for the appended phrase if an appended phrase exists
808 * @param tags
809 * @param nonViralName
810 */
811 protected void addAppendedTaggedPhrase(List<TaggedText> tags, INonViralName nonViralName){
812 String appendedPhrase = nonViralName ==null ? null : nonViralName.getAppendedPhrase();
813 if (isNotBlank(appendedPhrase)){
814 tags.add(new TaggedText(TagEnum.name, appendedPhrase));
815 }
816 }
817
818 @Override
819 public String getLastEpithet(TaxonName taxonName) {
820 Rank rank = taxonName.getRank();
821 if(rank.isGenus() || rank.isSupraGeneric()) {
822 return taxonName.getGenusOrUninomial();
823 } else if(rank.isInfraGeneric()) {
824 return taxonName.getInfraGenericEpithet();
825 } else if(rank.isSpecies()) {
826 return taxonName.getSpecificEpithet();
827 } else {
828 return taxonName.getInfraSpecificEpithet();
829 }
830 }
831
832
833 }