Project

General

Profile

Download (30.7 KB) Statistics
| Branch: | Tag: | Revision:
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
            String basionymPart = "";
184
            String authorPart = "";
185
            //basionym
186
            if (basionymAuthor != null || exBasionymAuthor != null){
187
                basionymPart = basionymStart + getAuthorAndExAuthor(basionymAuthor, exBasionymAuthor) + basionymEnd;
188
            }
189
            if (combinationAuthor != null || exCombinationAuthor != null){
190
                authorPart = getAuthorAndExAuthor(combinationAuthor, exCombinationAuthor);
191
            }
192
            result = CdmUtils.concat(basionymAuthorCombinationAuthorSeperator, basionymPart, authorPart);
193
//        if ("".equals(result)){
194
//        	result = null;
195
//        }
196
            return result;
197
        }
198
    }
199

    
200
    protected String getZoologicalNonCacheAuthorshipCache(TaxonName nonViralName) {
201
        if (nonViralName == null){
202
            return null;
203
        }
204
        String result = "";
205
        INomenclaturalAuthor combinationAuthor = nonViralName.getCombinationAuthorship();
206
        INomenclaturalAuthor exCombinationAuthor = nonViralName.getExCombinationAuthorship();
207
        INomenclaturalAuthor basionymAuthor = nonViralName.getBasionymAuthorship();
208
        INomenclaturalAuthor exBasionymAuthor = nonViralName.getExBasionymAuthorship();
209
        Integer publicationYear = nonViralName.getPublicationYear();
210
        Integer originalPublicationYear = nonViralName.getOriginalPublicationYear();
211

    
212
        String basionymPart = "";
213
        String authorPart = "";
214
        //basionym
215
        if (basionymAuthor != null || exBasionymAuthor != null || originalPublicationYear != null ){
216
            String authorAndEx = getAuthorAndExAuthor(basionymAuthor, exBasionymAuthor);
217
            String originalPublicationYearString = originalPublicationYear == null ? null : String.valueOf(originalPublicationYear);
218
            String authorAndExAndYear = CdmUtils.concat(zooAuthorYearSeperator, authorAndEx, originalPublicationYearString );
219
            basionymPart = basionymStart + authorAndExAndYear + basionymEnd;
220
        }
221
        if (combinationAuthor != null || exCombinationAuthor != null){
222
            String authorAndEx = getAuthorAndExAuthor(combinationAuthor, exCombinationAuthor);
223
            String publicationYearString = publicationYear == null ? null : String.valueOf(publicationYear);
224
            authorPart = CdmUtils.concat(zooAuthorYearSeperator, authorAndEx, publicationYearString);
225
        }
226
        result = CdmUtils.concat(basionymAuthorCombinationAuthorSeperator, basionymPart, authorPart);
227
        if (result == null){
228
            result = "";
229
        }
230
        return result;
231
    }
232

    
233

    
234
    /**
235
     * Returns the AuthorCache part for a combination of an author and an ex author. This applies on
236
     * combination authors as well as on basionym/orginal combination authors.
237
     * The correct order is exAuthor ex author though some botanist do not know about and do it the
238
     * other way round. (see 46.4-46.6 ICBN (Vienna Code, 2006))
239
     */
240
    protected String getAuthorAndExAuthor(INomenclaturalAuthor author, INomenclaturalAuthor exAuthor){
241
        String authorString = "";
242
        String exAuthorString = "";
243
        if (author != null){
244
            authorString = getNomAuthorTitle(author);
245
        }
246
        if (exAuthor != null){
247
            exAuthorString = getNomAuthorTitle(exAuthor);
248
            exAuthorString += exAuthorSeperator;
249
        }
250
        String result = exAuthorString + authorString;
251
        return result;
252
    }
253

    
254
    private String getNomAuthorTitle(INomenclaturalAuthor author) {
255
        return CdmUtils.Nz(author.getNomenclaturalTitleCache());
256
    }
257

    
258
    /**
259
     * Checks if the given name should include the author in it's cached version.<BR>
260
     * This is usually the case but not for <i>species aggregates</i>.
261
     * @param nonViralName
262
     * @return
263
     */
264
    protected boolean nameIncludesAuthorship(INonViralName nonViralName){
265
        Rank rank = nonViralName.getRank();
266
        if (rank != null && rank.isSpeciesAggregate()){
267
            return false;
268
        }else{
269
            return true;
270
        }
271
    }
272

    
273
// ************* TAGGED NAME ***************************************/
274

    
275
    @Override
276
    protected List<TaggedText> doGetTaggedTitle(TaxonName taxonName) {
277
        List<TaggedText> tags = new ArrayList<>();
278
        if (taxonName.getNameType().isViral()){
279
            String acronym = taxonName.getAcronym();
280
            if (isNotBlank(taxonName.getAcronym())){
281
                //this is not according to the code
282
                tags.add(new TaggedText(TagEnum.name, acronym));
283
            }
284
            return tags;
285
        }else if (taxonName.isHybridFormula()){
286
            //hybrid formula
287
            String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
288
            boolean isFirst = true;
289
            List<HybridRelationship> rels = taxonName.getOrderedChildRelationships();
290
            for (HybridRelationship rel: rels){
291
                if (! isFirst){
292
                    tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
293
                }
294
                isFirst = false;
295
                tags.addAll(getTaggedTitle(rel.getParentName()));
296
            }
297
            return tags;
298
        }else if (taxonName.isAutonym()){
299
            //Autonym
300
            tags.addAll(handleTaggedAutonym(taxonName));
301
        }else{ //not Autonym
302
             List<TaggedText> nameTags = getTaggedName(taxonName);
303
            tags.addAll(nameTags);
304
            String authorCache = getAuthorshipCache(taxonName);
305
            if (isNotBlank(authorCache)){
306
                tags.add(new TaggedText(TagEnum.authors, authorCache));
307
            }
308
        }
309
        return tags;
310
    }
311

    
312
    /**
313
     * Returns the tag list of the name part (without author and reference).
314
     * @param taxonName
315
     * @return
316
     */
317
    @Override
318
    public List<TaggedText> getTaggedName(TaxonName taxonName) {
319
        if (taxonName == null){
320
            return null;
321
        }else if (taxonName.isViral()){
322
            return null;
323
        }
324
        List<TaggedText> tags = new ArrayList<>();
325
        Rank rank = taxonName.getRank();
326

    
327
        if (taxonName.isProtectedNameCache()){
328
            tags.add(new TaggedText(TagEnum.name, taxonName.getNameCache()));
329
        }else if (taxonName.isHybridFormula()){
330
            //hybrid formula
331
            //TODO graft-chimera (see also cultivars)
332
            String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
333
            boolean isFirst = true;
334
            List<HybridRelationship> rels = taxonName.getOrderedChildRelationships();
335
            for (HybridRelationship rel: rels){
336
                if (! isFirst){
337
                    tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
338
                }
339
                isFirst = false;
340
                tags.addAll(getTaggedName(rel.getParentName()));
341
            }
342
            return tags;
343

    
344
        }else if (rank == null){
345
            tags = getRanklessTaggedNameCache(taxonName);
346
		}else if (rank.isCultivar()){
347
			tags = getCultivarTaggedNameCache(taxonName);
348
        }else if (rank.isInfraSpecific()){
349
            tags = getInfraSpeciesTaggedNameCache(taxonName);
350
        }else if (rank.isSpecies() || isAggregateWithAuthorship(taxonName, rank) ){ //exception see #4288
351
            tags = getSpeciesTaggedNameCache(taxonName);
352
        }else if (rank.isInfraGeneric()){
353
            tags = getInfraGenusTaggedNameCache(taxonName);
354
        }else if (rank.isGenus()){
355
            tags = getGenusOrUninomialTaggedNameCache(taxonName);
356
        }else if (rank.isSupraGeneric()){
357
            tags = getGenusOrUninomialTaggedNameCache(taxonName);
358
        }else{
359
            tags = getRanklessTaggedNameCache(taxonName);
360
            logger.warn("Rank does not belong to a rank class: " + rank.getTitleCache() + ". Used rankless nameCache for name " + taxonName.getUuid());
361
        }
362
        //TODO handle appended phrase here instead of different places, check first if this is true for all
363
        //cases
364

    
365
        return tags;
366
    }
367

    
368
//***************************** PRIVATES ***************************************/
369

    
370
    private List<TaggedText> getCultivarTaggedNameCache(TaxonName taxonName) {
371
        List<TaggedText> scientificNameTags;
372
        TaggedTextBuilder builder = TaggedTextBuilder.NewInstance();
373
        if (isNotBlank(taxonName.getInfraSpecificEpithet())){
374
            scientificNameTags = getInfraSpeciesTaggedNameCache(taxonName);
375
        } else if (isNotBlank(taxonName.getSpecificEpithet())){
376
            scientificNameTags = getSpeciesTaggedNameCache(taxonName);
377
        } else if (isNotBlank(taxonName.getInfraGenericEpithet())){
378
            scientificNameTags = getInfraGenusTaggedNameCache(taxonName);
379
        } else /*if (isNotBlank(taxonName.getGenusOrUninomial())) */{
380
            scientificNameTags = getGenusOrUninomialTaggedNameCache(taxonName);
381
        }
382

    
383
        UUID rankUuid = taxonName.getRank().getUuid();
384
        boolean rankIsHandled = true;
385
        String cultivarStr = null;
386
        String groupStr = taxonName.getCultivarGroupEpithet();
387
        if (rankUuid.equals(Rank.uuidCultivar)){
388
            cultivarStr = surroundedCultivarEpithet(taxonName);
389
            if (isNotBlank(cultivarStr) && isNotBlank(groupStr)){
390
                groupStr = surroundGroupWithBracket(groupStr);
391
            }
392
            cultivarStr = CdmUtils.concat(" ", groupStr, cultivarStr);
393
        }else if (rankUuid.equals(Rank.uuidCultivarGroup)){
394
            cultivarStr = CdmUtils.concat(" ", groupStr, checkHasGroupEpithet(groupStr)? null: "Group");
395
        }else if (rankUuid.equals(Rank.uuidGrex)){
396
            cultivarStr = CdmUtils.concat(" ", groupStr, checkHasGrexEpithet(groupStr)? null: "grex");
397
        }else{
398
            rankIsHandled = false;
399
        }
400
        if (isNotBlank(cultivarStr)){
401
            builder.addAll(scientificNameTags);
402
            builder.add(TagEnum.cultivar, cultivarStr);
403
        }
404

    
405
        if (rankUuid.equals(Rank.uuidGraftChimaera)){
406
            //TODO not yet fully implemented
407
            cultivarStr = "+ " + taxonName.getGenusOrUninomial() + " " + surroundedCultivarEpithet(taxonName);
408
            builder.add(TagEnum.cultivar, cultivarStr);
409
        } else if (!rankIsHandled){
410
            throw new IllegalStateException("Unsupported rank " + taxonName.getRank().getTitleCache() + " for cultivar.");
411
        }
412

    
413
        return builder.getTaggedText();
414
    }
415

    
416
    private String surroundGroupWithBracket(String groupStr) {
417
        if (groupStr.matches(NonViralNameParserImplRegExBase.grex + "$")){
418
            return groupStr;
419
        }else if (groupStr.matches(".*" + NonViralNameParserImplRegExBase.grex + ".+")){
420
            Matcher matcher = Pattern.compile(NonViralNameParserImplRegExBase.grex + "\\s*" ).matcher(groupStr);
421
            matcher.find();
422
            return groupStr.substring(0, matcher.end()) + "("+ groupStr.substring(matcher.end())+ ")";
423
        }else{
424
            return "("+ groupStr + ")";
425
        }
426
    }
427

    
428
    private boolean checkHasGroupEpithet(String group) {
429

    
430
        String[] splits = group == null? new String[0]: group.split("\\s+");
431
        if (splits.length <= 1){
432
            return false;
433
        }else if (splits[0].matches(NonViralNameParserImplRegExBase.group)
434
                || splits[splits.length-1].matches(NonViralNameParserImplRegExBase.group)){
435
            return true;
436
        }else{
437
            return false;
438
        }
439
    }
440

    
441
    private boolean checkHasGrexEpithet(String grex) {
442
        String[] splits = grex == null? new String[0]: grex.split("\\s+");
443
        if (splits.length <= 1){
444
            return false;
445
        }else if (splits[splits.length-1].matches(NonViralNameParserImplRegExBase.grex)){
446
            return true;
447
        }else{
448
            return false;
449
        }
450
    }
451

    
452
    private String surroundedCultivarEpithet(TaxonName taxonName) {
453
        return cultivarStart + taxonName.getCultivarEpithet() + cultivarEnd;
454
    }
455

    
456
    private boolean isAggregateWithAuthorship(TaxonName nonViralName, Rank rank) {
457
		if (rank == null){
458
			return false;
459
		}else{
460
			return rank.isSpeciesAggregate() && ( isNotBlank(nonViralName.getAuthorshipCache()) || nonViralName.getNomenclaturalReference() != null );
461
		}
462
	}
463

    
464
	/**
465
     * Returns the tag list for an autonym taxon.
466
     *
467
     * @see NonViralName#isAutonym()
468
     * @see BotanicalName#isAutonym()
469
     * @param nonViralName
470
     * @return
471
     */
472
    private List<TaggedText> handleTaggedAutonym(TaxonName nonViralName) {
473
    	List<TaggedText> tags = null;
474
    	if (nonViralName.isInfraSpecific()){
475
	        //species part
476
	        tags = getSpeciesTaggedNameCache(nonViralName);
477

    
478
	        //author
479
	        String authorCache = getAuthorshipCache(nonViralName);
480
	        if (isNotBlank(authorCache)){
481
	            tags.add(new TaggedText(TagEnum.authors, authorCache));
482
	        }
483

    
484
	        //infra species marker
485
	        if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraSpecific()){
486
	            //TODO handle exception
487
	            logger.warn("Rank for autonym does not exist or is not lower than species !!");
488
	        }else{
489
	            String infraSpeciesMarker = nonViralName.getRank().getAbbreviation();
490
	            if (nonViralName.isTrinomHybrid()){
491
	                infraSpeciesMarker = CdmUtils.concat("", NOTHO, infraSpeciesMarker);
492
	            }
493
	            if (isNotBlank(infraSpeciesMarker)){
494
	                tags.add(new TaggedText(TagEnum.rank, infraSpeciesMarker));
495
	            }
496
	        }
497

    
498
	        //infra species
499
	        String infraSpeciesPart = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet()).trim();
500
	        if (isNotBlank(infraSpeciesPart)){
501
	            tags.add(new TaggedText(TagEnum.name, infraSpeciesPart));
502
	        }
503

    
504
        } else if (nonViralName.isInfraGeneric()){
505
        	//genus part
506
	       tags =getGenusOrUninomialTaggedNameCache(nonViralName);
507

    
508
	       //author
509
           String authorCache = getAuthorshipCache(nonViralName);
510
           if (isNotBlank(authorCache)){
511
               tags.add(new TaggedText(TagEnum.authors, authorCache));
512
           }
513

    
514
	        //infra species marker
515
	        if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraGeneric()){
516
	            //TODO handle exception
517
	            logger.warn("Rank for autonym does not exist or is not lower than species !!");
518
	        }else{
519
	        	Rank rank = nonViralName.getRank();
520
	            String infraGenericMarker = rank.getAbbreviation();
521
                if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
522
                	infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
523
                }
524
	            if (isNotBlank(infraGenericMarker)){
525
	                tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
526
	            }
527
	        }
528

    
529
	        //infra genus
530
	        String infraGenericPart = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
531
	        if (isNotBlank(infraGenericPart)){
532
	            tags.add(new TaggedText(TagEnum.name, infraGenericPart));
533
	        }
534
        }
535

    
536
        return tags;
537
    }
538

    
539

    
540

    
541
    /**
542
     * Returns the tag list for rankless taxa.
543
     * @param nonViralName
544
     * @return
545
     */
546
    protected List<TaggedText> getRanklessTaggedNameCache(INonViralName nonViralName){
547
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
548
        String speciesEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
549
        if (isNotBlank(speciesEpi)){
550
            tags.add(new TaggedText(TagEnum.name, speciesEpi));
551
        }
552

    
553
        String infraSpeciesEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
554
        if (isNotBlank(infraSpeciesEpi)){
555
            tags.add(new TaggedText(TagEnum.name, infraSpeciesEpi));
556
        }
557

    
558
        //result += " (rankless)";
559
        addAppendedTaggedPhrase(tags, nonViralName);
560
        return tags;
561
    }
562

    
563
    /**
564
     * Returns the tag list for the first epithet (including a hybrid sign if required).
565
     * @param nonViralName
566
     * @return
567
     */
568
    private List<TaggedText> getUninomialTaggedPart(INonViralName nonViralName) {
569
        List<TaggedText> tags = new ArrayList<>();
570

    
571
        if (nonViralName.isMonomHybrid()){
572
            addHybridPrefix(tags);
573
        }
574

    
575
        String uninomial = CdmUtils.Nz(nonViralName.getGenusOrUninomial()).trim();
576
        if (isNotBlank(uninomial)){
577
            tags.add(new TaggedText(TagEnum.name, uninomial));
578
        }
579

    
580
        return tags;
581
    }
582

    
583
    /**
584
     * Returns the tag list for an genus or higher taxon.
585
     *
586
     * @param nonViralName
587
     * @return
588
     */
589
    protected List<TaggedText> getGenusOrUninomialTaggedNameCache(INonViralName nonViralName){
590
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
591
        addAppendedTaggedPhrase(tags, nonViralName);
592
        return tags;
593
    }
594

    
595
    /**
596
     * Returns the tag list for an infrageneric taxon (including species aggregates).
597
     *
598
     * @see #getSpeciesAggregateTaggedCache(NonViralName)
599
     * @param nonViralName
600
     * @return
601
     */
602
    protected List<TaggedText> getInfraGenusTaggedNameCache(INonViralName nonViralName){
603
        Rank rank = nonViralName.getRank();
604
        if (rank != null && rank.isSpeciesAggregate() && isBlank(nonViralName.getAuthorshipCache())){
605
            return getSpeciesAggregateTaggedCache(nonViralName);
606
        }
607

    
608
        //genus
609
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
610

    
611
        //marker
612
        String infraGenericMarker;
613
        if (rank != null){
614
            try {
615
                infraGenericMarker = rank.getInfraGenericMarker();
616
                if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
617
                	infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
618
                }
619
            } catch (UnknownCdmTypeException e) {
620
                infraGenericMarker = "'unhandled infrageneric rank'";
621
            }
622
        }else{
623
        	infraGenericMarker = "'undefined infrageneric rank'";
624
        }
625
        String infraGenEpi = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
626
        if (nonViralName.isBinomHybrid()){
627
            infraGenericMarker = CdmUtils.concat("", NOTHO, infraGenericMarker);
628
        }
629

    
630
        addInfraGenericPart(nonViralName, tags, infraGenericMarker, infraGenEpi);
631

    
632
        addAppendedTaggedPhrase(tags, nonViralName);
633
        return tags;
634
    }
635

    
636

    
637
	/**
638
	 * Default implementation for the infrageneric part of a name.
639
	 * This is usually the infrageneric marker and the infrageneric epitheton. But may be
640
	 * implemented differently e.g. for zoological names the infrageneric epitheton
641
	 * may be surrounded by brackets and the marker left out.
642
	 * @param nonViralName
643
	 * @param tags
644
	 * @param infraGenericMarker
645
	 */
646
	protected void addInfraGenericPart(
647
	        @SuppressWarnings("unused") INonViralName name,
648
	        List<TaggedText> tags,
649
	        String infraGenericMarker,
650
	        String infraGenEpi) {
651
		//add marker
652
		tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
653

    
654
		//add epitheton
655
		if (isNotBlank(infraGenEpi)){
656
            tags.add(new TaggedText(TagEnum.name, infraGenEpi));
657
        }
658
	}
659

    
660
    /**
661
     * Returns the tag list for a species aggregate (or similar) taxon.<BR>
662
     * Possible ranks for a <i>species aggregate</i> are "aggr.", "species group", ...
663
     * @param nonViralName
664
     * @return
665
     */
666
    protected List<TaggedText> getSpeciesAggregateTaggedCache(INonViralName nonViralName){
667
        if (! isBlank(nonViralName.getAuthorshipCache())){
668
        	List<TaggedText> result = getSpeciesTaggedNameCache(nonViralName);
669
        	return result;
670
        }
671

    
672

    
673
    	List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
674

    
675
        addSpeciesAggregateTaggedEpithet(tags, nonViralName);
676
        addAppendedTaggedPhrase(tags, nonViralName);
677
        return tags;
678
    }
679

    
680
    /**
681
     * Adds the aggregate tag to the tag list.
682
     * @param tags
683
     * @param nonViralName
684
     */
685
    private void addSpeciesAggregateTaggedEpithet(List<TaggedText> tags, INonViralName nonViralName) {
686
        String marker;
687
        try {
688
            marker = nonViralName.getRank().getInfraGenericMarker();
689
        } catch (UnknownCdmTypeException e) {
690
            marker = "'unknown aggregat type'";
691
        }
692
        if (isNotBlank(marker)){
693
            tags.add(new TaggedText(TagEnum.rank, marker));
694
        }
695
    }
696

    
697

    
698
    /**
699
     * Returns the tag list for a species taxon.
700
     * @param nonViralName
701
     * @return
702
     */
703
    protected List<TaggedText> getSpeciesTaggedNameCache(INonViralName nonViralName){
704
        List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
705
        addAppendedTaggedPhrase(tags, nonViralName);
706
        return tags;
707
    }
708

    
709
    protected List<TaggedText> getInfraSpeciesTaggedNameCache(TaxonName name){
710
        if (name.getNameType().isZoological()){
711
            boolean includeMarker =includeInfraSpecificMarkerForZooNames(name);
712
            return getInfraSpeciesTaggedNameCache(name, includeMarker);
713
        }else{
714
            return getInfraSpeciesTaggedNameCache(name, true);
715
        }
716
    }
717

    
718
    protected boolean includeInfraSpecificMarkerForZooNames(TaxonName name){
719
        return ! (name.isAutonym());  //only exclude marker if autonym, see also ZooNameNoMarkerCacheStrategy
720
    }
721

    
722
    /**
723
     * Creates the tag list for an infraspecific taxon. If include is true the result will contain
724
     * the infraspecific marker (e.g. "var.")
725
     * @param nonViralName
726
     * @param includeMarker
727
     * @return
728
     */
729
    protected List<TaggedText> getInfraSpeciesTaggedNameCache(INonViralName nonViralName, boolean includeMarker){
730
        List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
731
        if (includeMarker || nonViralName.isTrinomHybrid()){
732
            String marker = (nonViralName.getRank().getAbbreviation()).trim().replace("null", "");
733
            if (nonViralName.isTrinomHybrid()){
734
                marker = CdmUtils.concat("", NOTHO, marker);
735
            }
736
            if (isNotBlank(marker)){
737
                tags.add(new TaggedText(TagEnum.rank, marker));
738
            }
739

    
740
        }
741
        String infrSpecEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
742

    
743
        infrSpecEpi = infrSpecEpi.trim().replace("null", "");
744

    
745
        if (isNotBlank(infrSpecEpi)){
746
            tags.add(new TaggedText(TagEnum.name, infrSpecEpi));
747
        }
748

    
749
        addAppendedTaggedPhrase(tags, nonViralName);
750
        return tags;
751
    }
752

    
753

    
754
    /**
755
     * Adds a tag for the hybrid sign and an empty separator to avoid trailing whitespaces.
756
     * @param tags
757
     */
758
    private void addHybridPrefix(List<TaggedText> tags) {
759
        tags.add(new TaggedText(TagEnum.hybridSign, NonViralNameParserImplRegExBase.hybridSign));
760
        tags.add(new TaggedText(TagEnum.separator, "")); //no whitespace separator
761
    }
762

    
763
    /**
764
     * Creates the tag list for the genus and species part.
765
     * @param nonViralName
766
     * @return
767
     */
768
    private List<TaggedText> getGenusAndSpeciesTaggedPart(INonViralName nonViralName) {
769
        //Uninomial
770
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
771

    
772
        //InfraGenericEpi
773
        boolean hasInfraGenericEpi = isNotBlank(nonViralName.getInfraGenericEpithet());
774
        if (hasInfraGenericEpi){
775
            String infrGenEpi = nonViralName.getInfraGenericEpithet().trim();
776
            if (nonViralName.isBinomHybrid()){
777
                //maybe not correct but not really expected to happen
778
                infrGenEpi = UTF8.HYBRID + infrGenEpi;
779
            }
780
            infrGenEpi = "(" + infrGenEpi + ")";
781
            tags.add(new TaggedText(TagEnum.name, infrGenEpi));
782
        }
783

    
784
        //Species Epi
785
        String specEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
786
        if (! hasInfraGenericEpi && nonViralName.isBinomHybrid() ||
787
                hasInfraGenericEpi && nonViralName.isTrinomHybrid()){
788
            addHybridPrefix(tags);
789
        }
790
        if (isNotBlank(specEpi)){
791
            tags.add(new TaggedText(TagEnum.name, specEpi));
792
        }
793
        return tags;
794
    }
795

    
796
    /**
797
     * Adds the tag for the appended phrase if an appended phrase exists
798
     * @param tags
799
     * @param nonViralName
800
     */
801
    protected void addAppendedTaggedPhrase(List<TaggedText> tags, INonViralName nonViralName){
802
        String appendedPhrase = nonViralName ==null ? null : nonViralName.getAppendedPhrase();
803
        if (isNotBlank(appendedPhrase)){
804
            tags.add(new TaggedText(TagEnum.name, appendedPhrase));
805
        }
806
    }
807

    
808
	@Override
809
    public String getLastEpithet(TaxonName taxonName) {
810
        Rank rank = taxonName.getRank();
811
        if(rank.isGenus() || rank.isSupraGeneric()) {
812
            return taxonName.getGenusOrUninomial();
813
        } else if(rank.isInfraGeneric()) {
814
            return taxonName.getInfraGenericEpithet();
815
        } else if(rank.isSpecies()) {
816
            return taxonName.getSpecificEpithet();
817
        } else {
818
            return taxonName.getInfraSpecificEpithet();
819
        }
820
    }
821

    
822

    
823
}
(6-6/7)