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