hybrid parsing and caching for monom, binom and trinom flags implemented
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / strategy / cache / name / NonViralNameDefaultCacheStrategy.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.hibernate.HibernateProxyHelper;
22 import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
23 import eu.etaxonomy.cdm.model.agent.Team;
24 import eu.etaxonomy.cdm.model.common.Language;
25 import eu.etaxonomy.cdm.model.common.Representation;
26 import eu.etaxonomy.cdm.model.name.NomenclaturalStatus;
27 import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
28 import eu.etaxonomy.cdm.model.name.NonViralName;
29 import eu.etaxonomy.cdm.model.name.Rank;
30 import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
31 import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
32 import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImplRegExBase;
33
34
35 /**
36 * This class is a default implementation for the INonViralNameCacheStrategy<T extends NonViralName> interface.
37 * The method actually implements a cache strategy for botanical names so no method has to be overwritten by
38 * a subclass for botanic names.
39 * Where differing from this Default BotanicNameCacheStrategy other subclasses should overwrite the existing methods
40 * e.g. a CacheStrategy for zoological names should overwrite getAuthorAndExAuthor
41 * @author a.mueller
42 */
43 /**
44 * @author AM
45 *
46 * @param <T>
47 */
48 public class NonViralNameDefaultCacheStrategy<T extends NonViralName> extends NameCacheStrategyBase<T> implements INonViralNameCacheStrategy<T> {
49 private static final Logger logger = Logger.getLogger(NonViralNameDefaultCacheStrategy.class);
50
51 final static UUID uuid = UUID.fromString("1cdda0d1-d5bc-480f-bf08-40a510a2f223");
52
53 protected String NameAuthorSeperator = " ";
54 protected String BasionymStart = "(";
55 protected String BasionymEnd = ")";
56 protected String ExAuthorSeperator = " ex ";
57 protected CharSequence BasionymAuthorCombinationAuthorSeperator = " ";
58
59 @Override
60 public UUID getUuid(){
61 return uuid;
62 }
63
64
65 /**
66 * Factory method
67 * @return NonViralNameDefaultCacheStrategy A new instance of NonViralNameDefaultCacheStrategy
68 */
69 public static NonViralNameDefaultCacheStrategy NewInstance(){
70 return new NonViralNameDefaultCacheStrategy();
71 }
72
73 /**
74 * Constructor
75 */
76 protected NonViralNameDefaultCacheStrategy(){
77 super();
78 }
79
80 /* **************** GETTER / SETTER **************************************/
81
82 /**
83 * String that separates the NameCache part from the AuthorCache part
84 * @return
85 */
86 public String getNameAuthorSeperator() {
87 return NameAuthorSeperator;
88 }
89
90
91 public void setNameAuthorSeperator(String nameAuthorSeperator) {
92 NameAuthorSeperator = nameAuthorSeperator;
93 }
94
95
96 /**
97 * String the basionym author part starts with e.g. '('.
98 * This should correspond with the {@link NonViralNameDefaultCacheStrategy#getBasionymEnd() basionymEnd} attribute
99 * @return
100 */
101 public String getBasionymStart() {
102 return BasionymStart;
103 }
104
105
106 public void setBasionymStart(String basionymStart) {
107 BasionymStart = basionymStart;
108 }
109
110
111 /**
112 * String the basionym author part ends with e.g. ')'.
113 * This should correspond with the {@link NonViralNameDefaultCacheStrategy#getBasionymStart() basionymStart} attribute
114 * @return
115 */
116 public String getBasionymEnd() {
117 return BasionymEnd;
118 }
119
120
121 public void setBasionymEnd(String basionymEnd) {
122 BasionymEnd = basionymEnd;
123 }
124
125
126 /**
127 * String to seperate ex author from author.
128 * @return
129 */
130 public String getExAuthorSeperator() {
131 return ExAuthorSeperator;
132 }
133
134
135 public void setExAuthorSeperator(String exAuthorSeperator) {
136 ExAuthorSeperator = exAuthorSeperator;
137 }
138
139
140 /**
141 * String that seperates the basionym/original_combination author part from the combination author part
142 * @return
143 */
144 public CharSequence getBasionymAuthorCombinationAuthorSeperator() {
145 return BasionymAuthorCombinationAuthorSeperator;
146 }
147
148
149 public void setBasionymAuthorCombinationAuthorSeperator(
150 CharSequence basionymAuthorCombinationAuthorSeperator) {
151 BasionymAuthorCombinationAuthorSeperator = basionymAuthorCombinationAuthorSeperator;
152 }
153
154
155 //** *****************************************************************************************/
156
157
158 /* (non-Javadoc)
159 * @see eu.etaxonomy.cdm.strategy.INameCacheStrategy#getNameCache()
160 */
161 @Override
162 public String getTitleCache(T nonViralName) {
163 if (nonViralName == null){
164 return null;
165 }
166
167 if (nonViralName.isProtectedTitleCache()){
168 return nonViralName.getTitleCache();
169 }
170 String result = "";
171 //Autonym
172 if (nonViralName.isAutonym()){
173 result = handleAutonym(nonViralName);
174 }else{ //not Autonym
175 String nameCache = nonViralName.getNameCache(); //OLD: CdmUtils.Nz(getNameCache(nonViralName));
176 if (nameIncludesAuthorship(nonViralName)){
177 String authorCache = CdmUtils.Nz(getAuthorshipCache(nonViralName));
178 result = CdmUtils.concat(NameAuthorSeperator, nameCache, authorCache);
179 }else{
180 result = nameCache;
181 }
182 }
183 return result;
184 }
185
186
187 /**
188 * @param nonViralName
189 * @param speciesPart
190 * @return
191 */
192 private String handleAutonym(T nonViralName) {
193 String result;
194 String speciesPart = getSpeciesNameCache(nonViralName);
195 //TODO should this include basionym authors and ex authors
196 INomenclaturalAuthor author = nonViralName.getCombinationAuthorTeam();
197 String authorPart = "";
198 if (author != null){
199 authorPart = CdmUtils.Nz(author.getNomenclaturalTitle());
200 }
201 INomenclaturalAuthor basAuthor = nonViralName.getBasionymAuthorTeam();
202 String basAuthorPart = "";
203 if (basAuthor != null){
204 basAuthorPart = CdmUtils.Nz(basAuthor.getNomenclaturalTitle());
205 }
206 if (! "".equals(basAuthorPart)){
207 authorPart = "("+ basAuthorPart +")" + authorPart;
208 }
209 String infraSpeciesPart = (CdmUtils.Nz(nonViralName.getInfraSpecificEpithet()));
210
211 String infraSpeciesSeparator = "";
212 if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraSpecific()){
213 //TODO handle exception
214 logger.warn("Rank for autonym does not exist or is not lower than species !!");
215 }else{
216 infraSpeciesSeparator = nonViralName.getRank().getAbbreviation();
217 }
218
219 result = CdmUtils.concat(" ", new String[]{speciesPart, authorPart, infraSpeciesSeparator, infraSpeciesPart});
220 result = result.trim().replace("null", "");
221 return result;
222 }
223
224 protected boolean nameIncludesAuthorship(NonViralName nonViralName){
225 Rank rank = nonViralName.getRank();
226 if (rank != null && rank.isSpeciesAggregate()){
227 return false;
228 }else{
229 return true;
230 }
231 }
232
233
234
235
236
237 @Override
238 public String getFullTitleCache(T nonViralName) {
239 //null
240 if (nonViralName == null){
241 return null;
242 }
243 //full title cache
244 if (nonViralName.isProtectedFullTitleCache() == true) {
245 return nonViralName.getFullTitleCache();
246 }
247
248 String result = "";
249 //title cache
250 String titleCache = nonViralName.getTitleCache(); // OLD: getTitleCache(nonViralName);
251
252 String microReference = nonViralName.getNomenclaturalMicroReference();
253 INomenclaturalReference ref = nonViralName.getNomenclaturalReference();
254 String referenceBaseCache = null;
255 if (ref != null){
256 INomenclaturalReference nomenclaturalReference = HibernateProxyHelper.deproxy(ref, INomenclaturalReference.class);
257 nomenclaturalReference.setCacheStrategy(nomenclaturalReference.getType().getCacheStrategy());
258 referenceBaseCache = nomenclaturalReference.getNomenclaturalCitation(microReference);
259 }
260
261 //make nomenclatural status
262 String ncStatusCache = "";
263 Set<NomenclaturalStatus> ncStati = nonViralName.getStatus();
264 Iterator<NomenclaturalStatus> iterator = ncStati.iterator();
265 while (iterator.hasNext()) {
266 NomenclaturalStatus ncStatus = (NomenclaturalStatus)iterator.next();
267 // since the NewInstance method of nomencatural status allows null as parameter
268 // we have to check for null values here
269 String suffix = "not defined";
270 if(ncStatus.getType() != null){
271 NomenclaturalStatusType statusType = ncStatus.getType();
272 Language lang = Language.LATIN();
273 Representation repr = statusType.getRepresentation(lang);
274 if (repr != null){
275 suffix = repr.getAbbreviatedLabel();
276 }else{
277 String message = "No latin representation available for nom. status. " + statusType.getTitleCache();
278 logger.warn(message);
279 throw new IllegalStateException(message);
280 }
281 }else if(ncStatus.getRuleConsidered() != null && ! ncStatus.getRuleConsidered().equals("")){
282 suffix = ncStatus.getRuleConsidered();
283 }
284 ncStatusCache = ", " + suffix;
285 }
286 String refConcat = " ";
287 if (referenceBaseCache != null && ! referenceBaseCache.trim().startsWith("in ")){
288 refConcat = ", ";
289 }
290 result = CdmUtils.concat(refConcat, titleCache, referenceBaseCache);
291 result = CdmUtils.concat("", result, ncStatusCache);
292 return result;
293 }
294
295
296 /**
297 * Generates and returns the "name cache" (only scientific name without author teams and year).
298 * @see eu.etaxonomy.cdm.strategy.cache.name.INameCacheStrategy#getNameCache(eu.etaxonomy.cdm.model.name.TaxonNameBase)
299 */
300 public String getNameCache(T nonViralName) {
301 if (nonViralName == null){
302 return null;
303 }
304 String result;
305 Rank rank = nonViralName.getRank();
306
307 if (nonViralName.isProtectedNameCache()){
308 result = nonViralName.getNameCache();
309 }else if (rank == null){
310 result = getRanklessNameCache(nonViralName);
311 }else if (nonViralName.isInfragenericUnranked()){
312 result = getUnrankedInfragenericNameCache(nonViralName);
313 }else if (rank.isInfraSpecific()){
314 result = getInfraSpeciesNameCache(nonViralName);
315 }else if (rank.isSpecies()){
316 result = getSpeciesNameCache(nonViralName);
317 }else if (rank.isInfraGeneric()){
318 result = getInfraGenusNameCache(nonViralName);
319 }else if (rank.isGenus()){
320 result = getGenusOrUninomialNameCache(nonViralName);
321 }else if (rank.isSupraGeneric()){
322 result = getGenusOrUninomialNameCache(nonViralName);
323 }else{
324 logger.warn("Name Strategy for Name (UUID: " + nonViralName.getUuid() + ") not yet implemented");
325 result = "";
326 }
327 return result;
328 }
329 //
330 //
331 // /**
332 // * @param nonViralName
333 // * @return
334 // */
335 // private boolean isInfragenericUnranked(T nonViralName) {
336 // Rank rank = nonViralName.getRank();
337 // if (rank == null || ! rank.equals(Rank.UNRANKED())){
338 // return false;
339 // }
340 // if (StringUtils.isBlank(nonViralName.getSpecificEpithet()) && StringUtils.isBlank(nonViralName.getInfraSpecificEpithet()) ){
341 // return true;
342 // }else{
343 // return false;
344 // }
345 // }
346
347
348 private String getUnrankedInfragenericNameCache(T nonViralName) {
349 String result;
350 Rank rank = nonViralName.getRank();
351 if (rank.isSpeciesAggregate()){
352 return getSpeciesAggregateCache(nonViralName);
353 }
354 String infraGenericMarker = rank.getAbbreviation();
355 result = CdmUtils.Nz(nonViralName.getGenusOrUninomial()).trim();
356 result += " " + infraGenericMarker + " " + (CdmUtils.Nz(nonViralName.getInfraGenericEpithet())).trim().replace("null", "");
357 result = addAppendedPhrase(result, nonViralName).trim();
358 return result;
359 }
360
361
362 /* (non-Javadoc)
363 * @see eu.etaxonomy.cdm.strategy.cache.INonViralNameCacheStrategy#getAuthorCache(eu.etaxonomy.cdm.model.name.NonViralName)
364 */
365 public String getAuthorshipCache(T nonViralName) {
366 if (nonViralName == null){
367 return null;
368 }
369 //cache protected
370 if (nonViralName.isProtectedAuthorshipCache() == true) {
371 return nonViralName.getAuthorshipCache();
372 }
373 return getNonCacheAuthorshipCache(nonViralName);
374
375 }
376
377 /**
378 * Returns the authorshipcache string for the atomized authorship fields. Does not use the authorshipfield.
379 * @throws NullPointerException if nonViralName is null.
380 * @param nonViralName
381 * @return
382 */
383 protected String getNonCacheAuthorshipCache(T nonViralName){
384 String result = "";
385 INomenclaturalAuthor combinationAuthor = nonViralName.getCombinationAuthorTeam();
386 INomenclaturalAuthor exCombinationAuthor = nonViralName.getExCombinationAuthorTeam();
387 INomenclaturalAuthor basionymAuthor = nonViralName.getBasionymAuthorTeam();
388 INomenclaturalAuthor exBasionymAuthor = nonViralName.getExBasionymAuthorTeam();
389 String basionymPart = "";
390 String authorPart = "";
391 //basionym
392 if (basionymAuthor != null || exBasionymAuthor != null){
393 basionymPart = BasionymStart + getAuthorAndExAuthor(basionymAuthor, exBasionymAuthor) + BasionymEnd;
394 }
395 if (combinationAuthor != null || exCombinationAuthor != null){
396 authorPart = getAuthorAndExAuthor(combinationAuthor, exCombinationAuthor);
397 }
398 result = CdmUtils.concat(BasionymAuthorCombinationAuthorSeperator, basionymPart, authorPart);
399 return result;
400 }
401
402 /**
403 * Returns the AuthorCache part for a combination of an author and an ex author. This applies on combination authors
404 * as well as on basionym/orginal combination authors.
405 * @param author the author
406 * @param exAuthor the ex-author
407 * @return
408 */
409 protected String getAuthorAndExAuthor(INomenclaturalAuthor author, INomenclaturalAuthor exAuthor){
410 String result = "";
411 String authorString = "";
412 String exAuthorString = "";
413 if (author != null){
414 authorString = CdmUtils.Nz(author.getNomenclaturalTitle());
415 }
416 if (exAuthor != null){
417 exAuthorString = CdmUtils.Nz(exAuthor.getNomenclaturalTitle());
418 }
419 if (exAuthorString.length() > 0 ){
420 exAuthorString = exAuthorString + ExAuthorSeperator;
421 }
422 result = exAuthorString + authorString;
423 return result;
424
425 }
426
427
428 /* (non-Javadoc)
429 * @see eu.etaxonomy.cdm.strategy.INameCacheStrategy#getTaggedName(eu.etaxonomy.cdm.model.common.CdmBase)
430 */
431 @Override
432 public List<Object> getTaggedName(T nonViralName) {
433 List<Object> tags = new ArrayList<Object>();
434
435 if (nonViralName.isProtectedNameCache() ||
436 nonViralName.isProtectedAuthorshipCache() ||
437 nonViralName.isProtectedFullTitleCache() ||
438 nonViralName.isProtectedTitleCache()){
439 tags.add(nonViralName.getTitleCache());
440 return tags;
441 }
442
443 // Why does it make sense to add the nameCache in case of non existing genusOrUninomial?
444 // if (nonViralName.getGenusOrUninomial() == null){
445 // tags.add(nonViralName.getNameCache());
446 // }else{
447
448 if (nonViralName.getGenusOrUninomial() != null) {
449 tags.add(nonViralName.getGenusOrUninomial());
450 }
451 if (nonViralName.isSpecies() || nonViralName.isInfraSpecific()){
452 tags.add(nonViralName.getSpecificEpithet());
453 }
454
455 // No autonym
456 if (nonViralName.isInfraSpecific() && ! nonViralName.getSpecificEpithet().equals(nonViralName.getInfraSpecificEpithet())){
457 tags.add(nonViralName.getRank());
458 tags.add(nonViralName.getInfraSpecificEpithet());
459 }
460
461 if (nonViralName.isInfraGeneric()){
462 //TODO choose right strategy or generic approach?
463 // --- strategy 1 ---
464
465 if (nonViralName.getRank().isSpeciesAggregate()){
466 tags.add(nonViralName.getSpecificEpithet());
467 tags.add(getSpeciesAggregateEpithet(nonViralName));
468 }else{
469 tags.add(nonViralName.getRank());
470 tags.add(nonViralName.getInfraGenericEpithet());
471 }
472 // --- strategy 2 ---
473 // tags.add('('+nvn.getInfraGenericEpithet()+')');
474 }
475 Team authorTeam = Team.NewInstance();
476 authorTeam.setProtectedTitleCache(true);
477 authorTeam.setTitleCache(nonViralName.getAuthorshipCache(), true);
478 tags.add(authorTeam);
479
480 // Name is an autonym. Rank and infraspecific epitheton follow the author
481 if (nonViralName.isInfraSpecific() && nonViralName.getSpecificEpithet().equals(nonViralName.getInfraSpecificEpithet())){
482 tags.add(nonViralName.getRank());
483 tags.add(nonViralName.getInfraSpecificEpithet());
484 }
485
486 if(! "".equals(nonViralName.getAppendedPhrase())&& (nonViralName.getAppendedPhrase() != null)){
487 tags.add(nonViralName.getAppendedPhrase());
488 }
489
490 return tags;
491 }
492
493
494 //***************************** PRIVATES ***************************************/
495
496 protected String getRanklessNameCache(NonViralName nonViralName){
497 String result = "";
498 result = (result + (CdmUtils.Nz(nonViralName.getGenusOrUninomial()))).trim().replace("null", "");
499 result += " " + (CdmUtils.Nz(nonViralName.getSpecificEpithet())).trim();
500 result += " " + (CdmUtils.Nz(nonViralName.getInfraSpecificEpithet())).trim();
501 result = result.trim().replace("null", "");
502 //result += " (rankless)";
503 result = addAppendedPhrase(result, nonViralName);
504 return result;
505 }
506
507
508 protected String getGenusOrUninomialNameCache(NonViralName nonViralName){
509 String result;
510 result = getUninomialPart(nonViralName);
511 result = addAppendedPhrase(result, nonViralName).trim();
512 return result;
513 }
514
515
516 private String getUninomialPart(NonViralName nonViralName) {
517 String result;
518 result = CdmUtils.Nz(nonViralName.getGenusOrUninomial()).trim();
519 if (nonViralName.isMonomHybrid()){
520 result = NonViralNameParserImplRegExBase.hybridSign + result;
521 }
522 return result;
523 }
524
525 protected String getInfraGenusNameCache(NonViralName nonViralName){
526 String result;
527 Rank rank = nonViralName.getRank();
528 if (rank.isSpeciesAggregate()){
529 return getSpeciesAggregateCache(nonViralName);
530 }
531 String infraGenericMarker = "'unhandled infrageneric rank'";
532 if (rank != null){
533 try {
534 infraGenericMarker = rank.getInfraGenericMarker();
535 } catch (UnknownCdmTypeException e) {
536 infraGenericMarker = "'unhandled infrageneric rank'";
537 }
538 }
539 result = getUninomialPart(nonViralName);
540 result += " " + infraGenericMarker + " " + (CdmUtils.Nz(nonViralName.getInfraGenericEpithet())).trim().replace("null", "");
541 result = addAppendedPhrase(result, nonViralName).trim();
542 return result;
543 }
544
545 // aggr.|agg.|group
546 protected String getSpeciesAggregateCache(NonViralName nonViralName){
547 String result = getGenusAndSpeciesPart(nonViralName);
548
549 result += " " + getSpeciesAggregateEpithet(nonViralName);
550 result = addAppendedPhrase(result, nonViralName).trim();
551 return result;
552 }
553
554 private String getSpeciesAggregateEpithet(NonViralName nonViralName) {
555 String marker;
556 try {
557 marker = nonViralName.getRank().getInfraGenericMarker();
558 } catch (UnknownCdmTypeException e) {
559 marker = "'unknown aggregat type'";
560 }
561 return marker;
562 }
563
564 protected String getSpeciesNameCache(NonViralName nonViralName){
565 String result = getGenusAndSpeciesPart(nonViralName);
566 result = addAppendedPhrase(result, nonViralName).trim();
567 result = result.replace("\\s\\", " ");
568 return result;
569 }
570
571
572 protected String getInfraSpeciesNameCache(NonViralName nonViralName){
573 return getInfraSpeciesNameCache(nonViralName, true);
574 }
575
576 protected String getInfraSpeciesNameCache(NonViralName nonViralName, boolean includeMarker){
577 String result = getGenusAndSpeciesPart(nonViralName);
578 if (includeMarker){
579 result += " " + (nonViralName.getRank().getAbbreviation()).trim().replace("null", "");
580 }
581 String infrSpecEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
582 if (nonViralName.isTrinomHybrid()){
583 infrSpecEpi = NonViralNameParserImplRegExBase.hybridSign + infrSpecEpi;
584 }
585 result += " " + (infrSpecEpi).trim().replace("null", "");
586 result = addAppendedPhrase(result, nonViralName).trim();
587 return result;
588 }
589
590
591 private String getGenusAndSpeciesPart(NonViralName nonViralName) {
592 String result;
593 //Uninomial
594 result = getUninomialPart(nonViralName);
595
596 //InfraGenericEpi
597 boolean hasInfraGenericEpi = StringUtils.isNotBlank(nonViralName.getInfraGenericEpithet());
598 if (hasInfraGenericEpi){
599 String infrGenEpi = nonViralName.getInfraGenericEpithet().trim();
600 if (nonViralName.isBinomHybrid()){
601 infrGenEpi = NonViralNameParserImplRegExBase.hybridSign + infrGenEpi;
602 }
603 result += " (" + infrGenEpi + ")";
604 }
605 //Species Epi
606 String specEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
607 if (! hasInfraGenericEpi && nonViralName.isBinomHybrid() ||
608 hasInfraGenericEpi && nonViralName.isTrinomHybrid()){
609 specEpi = NonViralNameParserImplRegExBase.hybridSign + specEpi;
610 }
611 result += " " + (specEpi).replace("null", "");
612 return result;
613 }
614
615
616 protected String addAppendedPhrase(String resultString, NonViralName nonViralName){
617 String appendedPhrase = nonViralName ==null ? null : nonViralName.getAppendedPhrase();
618 if (resultString == null){
619 return appendedPhrase;
620 }else if(appendedPhrase == null || "".equals(appendedPhrase.trim())) {
621 return resultString;
622 }else if ("".equals(resultString)){
623 return resultString + appendedPhrase;
624 }else {
625 return resultString + " " + appendedPhrase;
626 }
627 }
628
629
630 public String getLastEpithet(T taxonNameBase) {
631 Rank rank = taxonNameBase.getRank();
632 if(rank.isGenus() || rank.isSupraGeneric()) {
633 return taxonNameBase.getGenusOrUninomial();
634 } else if(rank.isInfraGeneric()) {
635 return taxonNameBase.getInfraGenericEpithet();
636 } else if(rank.isSpecies()) {
637 return taxonNameBase.getSpecificEpithet();
638 } else {
639 return taxonNameBase.getInfraSpecificEpithet();
640 }
641 }
642 }