Project

General

Profile

Download (32.8 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.logging.log4j.LogManager;
18
import org.apache.logging.log4j.Logger;
19

    
20
import eu.etaxonomy.cdm.common.CdmUtils;
21
import eu.etaxonomy.cdm.common.UTF8;
22
import eu.etaxonomy.cdm.model.agent.Team;
23
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
24
import eu.etaxonomy.cdm.model.common.CdmBase;
25
import eu.etaxonomy.cdm.model.name.HybridRelationship;
26
import eu.etaxonomy.cdm.model.name.INonViralName;
27
import eu.etaxonomy.cdm.model.name.Rank;
28
import eu.etaxonomy.cdm.model.name.TaxonName;
29
import eu.etaxonomy.cdm.strategy.cache.TagEnum;
30
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
31
import eu.etaxonomy.cdm.strategy.cache.TaggedTextBuilder;
32
import eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy;
33
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
34
import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImplRegExBase;
35

    
36
/**
37
 * This class is a default implementation for the INonViralNameCacheStrategy<T extends NonViralName>
38
 * interface.<BR>
39
 * The method 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 botanic name strategy other subclasses should overwrite the
42
 * existing methods, e.g. a CacheStrategy for zoological names should overwrite getAuthorAndExAuthor
43
 * @author a.mueller
44
 */
45
public class TaxonNameDefaultCacheStrategy
46
        extends NameCacheStrategyBase
47
        implements INonViralNameCacheStrategy {
48

    
49
    private static final Logger logger = LogManager.getLogger();
50
	private static final long serialVersionUID = -6577757501563212669L;
51

    
52
    final static UUID uuid = UUID.fromString("1cdda0d1-d5bc-480f-bf08-40a510a2f223");
53

    
54
    protected String nameAuthorSeperator = " ";
55
    protected String basionymStart = "(";
56
    protected String basionymEnd = ")";
57
    protected String exAuthorSeperator = " ex ";
58
    private static String NOTHO = "notho";
59
    private CharSequence basionymAuthorCombinationAuthorSeperator = " ";
60

    
61
    private String zooAuthorYearSeperator = ", ";
62
    private Integer etAlPosition;
63

    
64
    private String cultivarStart = "'";
65
    private String cultivarEnd = "'";
66

    
67
    @Override
68
    public UUID getUuid(){
69
        return uuid;
70
    }
71

    
72
// ************************** FACTORY  ******************************/
73

    
74
    public static TaxonNameDefaultCacheStrategy NewInstance(){
75
        return new TaxonNameDefaultCacheStrategy();
76
    }
77

    
78
// ************ CONSTRUCTOR *******************/
79

    
80
    protected TaxonNameDefaultCacheStrategy(){
81
        super();
82
    }
83

    
84
/* **************** GETTER / SETTER **************************************/
85

    
86
    @Override
87
    public Integer getEtAlPosition() {
88
        return etAlPosition;
89
    }
90

    
91
    @Override
92
    public void setEtAlPosition(Integer etAlPosition) {
93
        this.etAlPosition = etAlPosition;
94
    }
95

    
96
    /**
97
     * String that separates the NameCache part from the AuthorCache part
98
     * @return
99
     */
100
    public String getNameAuthorSeperator() {
101
        return nameAuthorSeperator;
102
    }
103
    public void setNameAuthorSeperator(String nameAuthorSeperator) {
104
        this.nameAuthorSeperator = nameAuthorSeperator;
105
    }
106

    
107
    /**
108
     * String the basionym author part starts with e.g. '('.
109
     * This should correspond with the {@link TaxonNameDefaultCacheStrategy#getBasionymEnd() basionymEnd} attribute
110
     * @return
111
     */
112
    public String getBasionymStart() {
113
        return basionymStart;
114
    }
115
    public void setBasionymStart(String basionymStart) {
116
        this.basionymStart = basionymStart;
117
    }
118

    
119
    /**
120
     * String the basionym author part ends with e.g. ')'.
121
     * This should correspond with the {@link TaxonNameDefaultCacheStrategy#getBasionymStart() basionymStart} attribute
122
     * @return
123
     */
124
    public String getBasionymEnd() {
125
        return basionymEnd;
126
    }
127
    public void setBasionymEnd(String basionymEnd) {
128
        this.basionymEnd = basionymEnd;
129
    }
130

    
131
    /**
132
     * String to separate ex author from author.
133
     */
134
    public String getExAuthorSeperator() {
135
        return exAuthorSeperator;
136
    }
137
    public void setExAuthorSeperator(String exAuthorSeperator) {
138
        this.exAuthorSeperator = exAuthorSeperator;
139
    }
140

    
141
    /**
142
     * String that separates the basionym/original_combination author part from the combination author part
143
     */
144
    public CharSequence getBasionymAuthorCombinationAuthorSeperator() {
145
        return basionymAuthorCombinationAuthorSeperator;
146
    }
147
    public void setBasionymAuthorCombinationAuthorSeperator( CharSequence basionymAuthorCombinationAuthorSeperator) {
148
        this.basionymAuthorCombinationAuthorSeperator = basionymAuthorCombinationAuthorSeperator;
149
    }
150

    
151
    public String getZooAuthorYearSeperator() {
152
        return zooAuthorYearSeperator;
153
    }
154
    public void setZooAuthorYearSeperator(String authorYearSeperator) {
155
        this.zooAuthorYearSeperator = authorYearSeperator;
156
    }
157

    
158
// ******************* Authorship ******************************/
159

    
160
    @Override
161
    public String getAuthorshipCache(TaxonName taxonName) {
162
        if (taxonName == null){
163
            return null;
164
        }else if (taxonName.isViral()){
165
            return null;
166
        }else if(taxonName.isProtectedAuthorshipCache() == true) {
167
            //cache protected
168
            return taxonName.getAuthorshipCache();
169
        }else{
170
            return getNonCacheAuthorshipCache(taxonName);
171
        }
172
    }
173

    
174
    /**
175
     * Returns the authorshipcache string for the atomized authorship fields. Does not use the authorship field.
176
     * @throws NullPointerException if nonViralName is null.
177
     * @param taxonName
178
     * @return
179
     */
180
    protected String getNonCacheAuthorshipCache(TaxonName nonViralName){
181
        if (nonViralName.getNameType().isZoological()){
182
            return this.getZoologicalNonCacheAuthorshipCache(nonViralName);
183
        }else{
184
            String result = "";
185
            TeamOrPersonBase<?> combinationAuthor = nonViralName.getCombinationAuthorship();
186
            TeamOrPersonBase<?> exCombinationAuthor = nonViralName.getExCombinationAuthorship();
187
            TeamOrPersonBase<?> basionymAuthor = nonViralName.getBasionymAuthorship();
188
            TeamOrPersonBase<?> exBasionymAuthor = nonViralName.getExBasionymAuthorship();
189
            if (isCultivar(nonViralName) ){
190
                exCombinationAuthor = null;
191
                basionymAuthor = null;
192
                exBasionymAuthor = null;
193
            }
194

    
195
            String basionymPart = "";
196
            String authorPart = "";
197
            //basionym
198
            if (basionymAuthor != null || exBasionymAuthor != null){
199
                basionymPart = basionymStart + getAuthorAndExAuthor(basionymAuthor, exBasionymAuthor) + basionymEnd;
200
            }
201
            if (combinationAuthor != null || exCombinationAuthor != null){
202
                authorPart = getAuthorAndExAuthor(combinationAuthor, exCombinationAuthor);
203
            }
204
            result = CdmUtils.concat(basionymAuthorCombinationAuthorSeperator, basionymPart, authorPart);
205
//        if ("".equals(result)){
206
//        	result = null;
207
//        }
208
            return result;
209
        }
210
    }
211

    
212
    private boolean isCultivar(TaxonName name) {
213
        return name.isCultivar() || isNotBlank(name.getCultivarEpithet()) || isNotBlank(name.getCultivarGroupEpithet());
214
    }
215

    
216
    protected String getZoologicalNonCacheAuthorshipCache(TaxonName nonViralName) {
217
        if (nonViralName == null){
218
            return null;
219
        }
220
        String result = "";
221
        TeamOrPersonBase<?> combinationAuthor = nonViralName.getCombinationAuthorship();
222
        TeamOrPersonBase<?> exCombinationAuthor = nonViralName.getExCombinationAuthorship();
223
        TeamOrPersonBase<?> basionymAuthor = nonViralName.getBasionymAuthorship();
224
        TeamOrPersonBase<?> exBasionymAuthor = nonViralName.getExBasionymAuthorship();
225
        Integer publicationYear = nonViralName.getPublicationYear();
226
        Integer originalPublicationYear = nonViralName.getOriginalPublicationYear();
227

    
228
        String basionymPart = "";
229
        String authorPart = "";
230
        //basionym
231
        if (basionymAuthor != null || exBasionymAuthor != null || originalPublicationYear != null ){
232
            String authorAndEx = getAuthorAndExAuthor(basionymAuthor, exBasionymAuthor);
233
            String originalPublicationYearString = originalPublicationYear == null ? null : String.valueOf(originalPublicationYear);
234
            String authorAndExAndYear = CdmUtils.concat(zooAuthorYearSeperator, authorAndEx, originalPublicationYearString );
235
            basionymPart = basionymStart + authorAndExAndYear + basionymEnd;
236
        }
237
        if (combinationAuthor != null || exCombinationAuthor != null){
238
            String authorAndEx = getAuthorAndExAuthor(combinationAuthor, exCombinationAuthor);
239
            String publicationYearString = publicationYear == null ? null : String.valueOf(publicationYear);
240
            authorPart = CdmUtils.concat(zooAuthorYearSeperator, authorAndEx, publicationYearString);
241
        }
242
        result = CdmUtils.concat(basionymAuthorCombinationAuthorSeperator, basionymPart, authorPart);
243
        if (result == null){
244
            result = "";
245
        }
246
        return result;
247
    }
248

    
249

    
250
    /**
251
     * Returns the AuthorCache part for a combination of an author and an ex author. This applies on
252
     * combination authors as well as on basionym/orginal combination authors.
253
     * The correct order is exAuthor ex author though some botanist do not know about and do it the
254
     * other way round. (see 46.4-46.6 ICBN (Vienna Code, 2006))
255
     */
256
    protected String getAuthorAndExAuthor(TeamOrPersonBase<?> author, TeamOrPersonBase<?> exAuthor){
257
        String authorString = "";
258
        String exAuthorString = "";
259
        if (author != null){
260
            authorString = getNomAuthorTitle(author);
261
        }
262
        if (exAuthor != null){
263
            exAuthorString = getNomAuthorTitle(exAuthor);
264
            exAuthorString += exAuthorSeperator;
265
        }
266
        String result = exAuthorString + authorString;
267
        return result;
268
    }
269

    
270
    private String getNomAuthorTitle(TeamOrPersonBase<?>  author) {
271
        if (!author.isInstanceOf(Team.class) || this.getEtAlPosition() == null || this.getEtAlPosition() < 2) {
272
            return CdmUtils.Nz(author.getNomenclaturalTitleCache());
273
        }else {
274
            Team team = CdmBase.deproxy(author, Team.class);
275
            TeamDefaultCacheStrategy formatter = TeamDefaultCacheStrategy.NewInstanceNomEtAl(this.getEtAlPosition());
276
            return formatter.getNomenclaturalTitleCache(team);
277
        }
278
    }
279

    
280
    /**
281
     * Checks if the given name should include the author in it's cached version.<BR>
282
     * This is usually the case but not for <i>species aggregates</i>.
283
     * @param nonViralName
284
     * @return
285
     */
286
    protected boolean nameIncludesAuthorship(INonViralName nonViralName){
287
        Rank rank = nonViralName.getRank();
288
        if (rank != null && rank.isSpeciesAggregate()){
289
            return false;
290
        }else{
291
            return true;
292
        }
293
    }
294

    
295
// ************* TAGGED NAME ***************************************/
296

    
297
    @Override
298
    protected List<TaggedText> doGetTaggedTitle(TaxonName taxonName) {
299
        List<TaggedText> tags = new ArrayList<>();
300
        if (taxonName.getNameType().isViral()){
301
            String acronym = taxonName.getAcronym();
302
            if (isNotBlank(taxonName.getAcronym())){
303
                //this is not according to the code
304
                tags.add(new TaggedText(TagEnum.name, acronym));
305
            }
306
            return tags;
307
        }else if (taxonName.isHybridFormula()){
308
            //hybrid formula
309
            String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
310
            boolean isFirst = true;
311
            List<HybridRelationship> rels = taxonName.getOrderedChildRelationships();
312
            for (HybridRelationship rel: rels){
313
                if (! isFirst){
314
                    tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
315
                }
316
                isFirst = false;
317
                tags.addAll(getTaggedTitle(rel.getParentName()));
318
            }
319
            return tags;
320
        }else if (taxonName.isAutonym()){
321
            //Autonym
322
            tags.addAll(handleTaggedAutonym(taxonName, true));
323
        }else{ //not Autonym
324
            List<TaggedText> nameTags = getTaggedName(taxonName);
325
            tags.addAll(nameTags);
326
            String authorCache = getAuthorshipCache(taxonName);
327
            if (isNotBlank(authorCache)){
328
                tags.add(new TaggedText(TagEnum.authors, authorCache));
329
            }
330
        }
331
        return tags;
332
    }
333

    
334
    /**
335
     * Returns the tag list of the name part (without author and reference).
336
     * @param taxonName
337
     * @return
338
     */
339
    @Override
340
    public List<TaggedText> getTaggedName(TaxonName taxonName) {
341
        if (taxonName == null){
342
            return null;
343
        }else if (taxonName.isViral()){
344
            return null;
345
        }
346
        List<TaggedText> tags = new ArrayList<>();
347
        Rank rank = taxonName.getRank();
348

    
349
        if (taxonName.isProtectedNameCache()){
350
            tags.add(new TaggedText(TagEnum.name, taxonName.getNameCache()));
351
        }else if (taxonName.isHybridFormula()){
352
            //hybrid formula
353
            //TODO graft-chimera (see also cultivars)
354
            String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
355
            boolean isFirst = true;
356
            List<HybridRelationship> rels = taxonName.getOrderedChildRelationships();
357
            for (HybridRelationship rel: rels){
358
                if (! isFirst){
359
                    tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
360
                }
361
                isFirst = false;
362
                tags.addAll(getTaggedName(rel.getParentName()));
363
            }
364
            return tags;
365

    
366
        }else if (rank == null){
367
            tags = getRanklessTaggedNameCache(taxonName, true);
368
		}else if (rank.isCultivar()){
369
			tags = getCultivarTaggedNameCache(taxonName);
370
        }else if (rank.isInfraSpecific()){
371
            tags = getInfraSpeciesTaggedNameCache(taxonName);
372
        }else if (rank.isSpecies() || isAggregateWithAuthorship(taxonName, rank) ){ //exception see #4288
373
            tags = getSpeciesTaggedNameCache(taxonName, true);
374
        }else if (rank.isInfraGeneric()){
375
            tags = getInfraGenusTaggedNameCache(taxonName, true);
376
        }else if (rank.isGenus()){
377
            tags = getGenusOrUninomialTaggedNameCache(taxonName, true);
378
        }else if (rank.isSupraGeneric()){
379
            tags = getGenusOrUninomialTaggedNameCache(taxonName, true);
380
        }else{
381
            tags = getRanklessTaggedNameCache(taxonName, true);
382
            logger.warn("Rank does not belong to a rank class: " + rank.getTitleCache() + ". Used rankless nameCache for name " + taxonName.getUuid());
383
        }
384
        //TODO handle appended phrase here instead of different places, check first if this is true for all
385
        //cases
386

    
387
        return tags;
388
    }
389

    
390
//***************************** PRIVATES ***************************************/
391

    
392
    private List<TaggedText> getCultivarTaggedNameCache(TaxonName taxonName) {
393
        List<TaggedText> scientificNameTags;
394
        TaggedTextBuilder builder = TaggedTextBuilder.NewInstance();
395
        if (isNotBlank(taxonName.getInfraSpecificEpithet())){
396
            scientificNameTags = getInfraSpeciesTaggedNameCache(taxonName, false, false);
397
        } else if (isNotBlank(taxonName.getSpecificEpithet())){
398
            scientificNameTags = getSpeciesTaggedNameCache(taxonName, false);
399
        } else if (isNotBlank(taxonName.getInfraGenericEpithet())){
400
            scientificNameTags = getInfraGenusTaggedNameCache(taxonName, false);
401
        } else /*if (isNotBlank(taxonName.getGenusOrUninomial())) */{
402
            scientificNameTags = getGenusOrUninomialTaggedNameCache(taxonName, false);
403
        }
404

    
405
        UUID rankUuid = taxonName.getRank().getUuid();
406
        boolean rankIsHandled = true;
407
        String cultivarStr = null;
408
        String groupStr = taxonName.getCultivarGroupEpithet();
409
        if (rankUuid.equals(Rank.uuidCultivar)){
410
            cultivarStr = surroundedCultivarEpithet(taxonName.getCultivarEpithet());
411
            if (isNotBlank(cultivarStr) && isNotBlank(groupStr)){
412
                groupStr = surroundGroupWithBracket(groupStr);
413
            }
414
            cultivarStr = CdmUtils.concat(" ", groupStr, cultivarStr);
415
        }else if (rankUuid.equals(Rank.uuidCultivarGroup)){
416
            cultivarStr = CdmUtils.concat(" ", groupStr, checkHasGroupEpithet(groupStr)? null: "Group");
417
        }else if (rankUuid.equals(Rank.uuidGrexICNCP)){
418
            cultivarStr = CdmUtils.concat(" ", groupStr, checkHasGrexEpithet(groupStr)? null: "grex");
419
        }else{
420
            rankIsHandled = false;
421
        }
422
        if (rankIsHandled){
423
            builder.addAll(scientificNameTags);
424
        }else if (rankUuid.equals(Rank.uuidGraftChimaera)){
425
            //TODO not yet fully implemented
426
            cultivarStr = "+ " + CdmUtils.concat(" ", taxonName.getGenusOrUninomial(), surroundedCultivarEpithet(taxonName.getCultivarEpithet()));
427
        }else if (rankUuid.equals(Rank.uuidDenominationClass)){
428
            //TODO dummy implementation
429
            cultivarStr = CdmUtils.concat(" ", taxonName.getGenusOrUninomial(), surroundedCultivarEpithet(taxonName.getCultivarEpithet()));
430
        } else { //(!rankIsHandled)
431
            throw new IllegalStateException("Unsupported rank " + taxonName.getRank().getTitleCache() + " for cultivar.");
432
        }
433
        if (isNotBlank(cultivarStr)){
434
            builder.add(TagEnum.cultivar, cultivarStr);
435
        }
436

    
437
        List<TaggedText> tags = builder.getTaggedText();
438
        addAppendedTaggedPhrase(tags, taxonName, true);
439
        return tags;
440
    }
441

    
442
    private String surroundGroupWithBracket(String groupStr) {
443
        if (groupStr.matches(NonViralNameParserImplRegExBase.grex + "$")){
444
            return groupStr;
445
        }else if (groupStr.matches(".*" + NonViralNameParserImplRegExBase.grex + ".+")){
446
            Matcher matcher = Pattern.compile(NonViralNameParserImplRegExBase.grex + "\\s*" ).matcher(groupStr);
447
            matcher.find();
448
            return groupStr.substring(0, matcher.end()) + "("+ groupStr.substring(matcher.end())+ ")";
449
        }else{
450
            return "("+ groupStr + ")";
451
        }
452
    }
453

    
454
    private boolean checkHasGroupEpithet(String group) {
455

    
456
        String[] splits = group == null? new String[0]: group.split("\\s+");
457
        if (splits.length <= 1){
458
            return false;
459
        }else if (splits[0].matches(NonViralNameParserImplRegExBase.group)
460
                || splits[splits.length-1].matches(NonViralNameParserImplRegExBase.group)){
461
            return true;
462
        }else{
463
            return false;
464
        }
465
    }
466

    
467
    private boolean checkHasGrexEpithet(String grex) {
468
        String[] splits = grex == null? new String[0]: grex.split("\\s+");
469
        if (splits.length <= 1){
470
            return false;
471
        }else if (splits[splits.length-1].matches(NonViralNameParserImplRegExBase.grex)){
472
            return true;
473
        }else{
474
            return false;
475
        }
476
    }
477

    
478
    private String surroundedCultivarEpithet(String cultivarEpi) {
479
        return cultivarStart + CdmUtils.Nz(cultivarEpi) + cultivarEnd;
480
    }
481

    
482
    private boolean isAggregateWithAuthorship(TaxonName nonViralName, Rank rank) {
483
		if (rank == null){
484
			return false;
485
		}else{
486
			return rank.isSpeciesAggregate() && ( isNotBlank(nonViralName.getAuthorshipCache()) || nonViralName.getNomenclaturalReference() != null );
487
		}
488
	}
489

    
490
	/**
491
     * Returns the tag list for an autonym taxon.
492
     *
493
     * @see NonViralName#isAutonym()
494
     * @see BotanicalName#isAutonym()
495
     * @param nonViralName
496
     * @return
497
     */
498
    private List<TaggedText> handleTaggedAutonym(TaxonName nonViralName, boolean addAppended) {
499
    	List<TaggedText> tags = null;
500
    	if (nonViralName.isInfraSpecific()){
501
	        //species part
502
	        tags = getSpeciesTaggedNameCache(nonViralName, addAppended);
503

    
504
	        //author
505
	        String authorCache = getAuthorshipCache(nonViralName);
506
	        if (isNotBlank(authorCache)){
507
	            tags.add(new TaggedText(TagEnum.authors, authorCache));
508
	        }
509

    
510
	        //infra species marker
511
	        if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraSpecific()){
512
	            //TODO handle exception
513
	            logger.warn("Rank for autonym does not exist or is not lower than species !!");
514
	        }else{
515
	            String infraSpeciesMarker = nonViralName.getRank().getAbbreviation();
516
	            if (nonViralName.isTrinomHybrid()){
517
	                infraSpeciesMarker = CdmUtils.concat("", NOTHO, infraSpeciesMarker);
518
	            }
519
	            if (isNotBlank(infraSpeciesMarker)){
520
	                tags.add(new TaggedText(TagEnum.rank, infraSpeciesMarker));
521
	            }
522
	        }
523

    
524
	        //infra species
525
	        String infraSpeciesPart = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet()).trim();
526
	        if (isNotBlank(infraSpeciesPart)){
527
	            tags.add(new TaggedText(TagEnum.name, infraSpeciesPart));
528
	        }
529

    
530
        } else if (nonViralName.isInfraGeneric()){
531
        	//genus part
532
	       tags =getGenusOrUninomialTaggedNameCache(nonViralName, addAppended);
533

    
534
	       //author
535
           String authorCache = getAuthorshipCache(nonViralName);
536
           if (isNotBlank(authorCache)){
537
               tags.add(new TaggedText(TagEnum.authors, authorCache));
538
           }
539

    
540
	        //infra species marker
541
	        if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraGeneric()){
542
	            //TODO handle exception
543
	            logger.warn("Rank for autonym does not exist or is not lower than species !!");
544
	        }else{
545
	        	Rank rank = nonViralName.getRank();
546
	            String infraGenericMarker = rank.getAbbreviation();
547
                if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
548
                	infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
549
                }
550
	            if (isNotBlank(infraGenericMarker)){
551
	                tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
552
	            }
553
	        }
554

    
555
	        //infra genus
556
	        String infraGenericPart = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
557
	        if (isNotBlank(infraGenericPart)){
558
	            tags.add(new TaggedText(TagEnum.name, infraGenericPart));
559
	        }
560
        }
561

    
562
        return tags;
563
    }
564

    
565

    
566

    
567
    /**
568
     * Returns the tag list for rankless taxa.
569
     * @param nonViralName
570
     * @return
571
     */
572
    protected List<TaggedText> getRanklessTaggedNameCache(INonViralName nonViralName, boolean addAppended){
573
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
574
        String speciesEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
575
        if (isNotBlank(speciesEpi)){
576
            tags.add(new TaggedText(TagEnum.name, speciesEpi));
577
        }
578

    
579
        String infraSpeciesEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
580
        if (isNotBlank(infraSpeciesEpi)){
581
            if (nonViralName.isTrinomHybrid()){
582
                tags.add(new TaggedText(TagEnum.rank, NOTHO));
583
            }
584
            tags.add(new TaggedText(TagEnum.name, infraSpeciesEpi));
585
        }
586

    
587
        //result += " (rankless)";
588
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
589
        return tags;
590
    }
591

    
592
    /**
593
     * Returns the tag list for the first epithet (including a hybrid sign if required).
594
     * @param nonViralName
595
     * @return
596
     */
597
    private List<TaggedText> getUninomialTaggedPart(INonViralName nonViralName) {
598
        List<TaggedText> tags = new ArrayList<>();
599

    
600
        if (nonViralName.isMonomHybrid()){
601
            addHybridPrefix(tags);
602
        }
603

    
604
        String uninomial = CdmUtils.Nz(nonViralName.getGenusOrUninomial()).trim();
605
        if (isNotBlank(uninomial)){
606
            tags.add(new TaggedText(TagEnum.name, uninomial));
607
        }
608

    
609
        return tags;
610
    }
611

    
612
    /**
613
     * Returns the tag list for an genus or higher taxon.
614
     *
615
     * @param nonViralName
616
     * @return
617
     */
618
    protected List<TaggedText> getGenusOrUninomialTaggedNameCache(INonViralName nonViralName, boolean addAppended){
619
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
620
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
621
        return tags;
622
    }
623

    
624
    /**
625
     * Returns the tag list for an infrageneric taxon (including species aggregates).
626
     *
627
     * @see #getSpeciesAggregateTaggedCache(NonViralName)
628
     * @param nonViralName
629
     * @return
630
     */
631
    protected List<TaggedText> getInfraGenusTaggedNameCache(INonViralName nonViralName, boolean addAppended){
632
        Rank rank = nonViralName.getRank();
633
        if (rank != null && rank.isSpeciesAggregate() && isBlank(nonViralName.getAuthorshipCache())){
634
            return getSpeciesAggregateTaggedCache(nonViralName, addAppended);
635
        }
636

    
637
        //genus
638
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
639

    
640
        //marker
641
        String infraGenericMarker;
642
        if (rank != null){
643
            try {
644
                infraGenericMarker = rank.getInfraGenericMarker();
645
                if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
646
                	infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
647
                }
648
            } catch (UnknownCdmTypeException e) {
649
                infraGenericMarker = "'unhandled infrageneric rank'";
650
            }
651
        }else{
652
        	infraGenericMarker = "'undefined infrageneric rank'";
653
        }
654
        String infraGenEpi = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
655
        if (nonViralName.isBinomHybrid()){
656
            infraGenericMarker = CdmUtils.concat("", NOTHO, infraGenericMarker);
657
        }
658

    
659
        addInfraGenericPart(nonViralName, tags, infraGenericMarker, infraGenEpi);
660

    
661
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
662
        return tags;
663
    }
664

    
665

    
666
	/**
667
	 * Default implementation for the infrageneric part of a name.
668
	 * This is usually the infrageneric marker and the infrageneric epitheton. But may be
669
	 * implemented differently e.g. for zoological names the infrageneric epitheton
670
	 * may be surrounded by brackets and the marker left out.
671
	 * @param nonViralName
672
	 * @param tags
673
	 * @param infraGenericMarker
674
	 */
675
	protected void addInfraGenericPart(
676
	        @SuppressWarnings("unused") INonViralName name,
677
	        List<TaggedText> tags,
678
	        String infraGenericMarker,
679
	        String infraGenEpi) {
680
		//add marker
681
		tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
682

    
683
		//add epitheton
684
		if (isNotBlank(infraGenEpi)){
685
            tags.add(new TaggedText(TagEnum.name, infraGenEpi));
686
        }
687
	}
688

    
689
    /**
690
     * Returns the tag list for a species aggregate (or similar) taxon.<BR>
691
     * Possible ranks for a <i>species aggregate</i> are "aggr.", "species group", ...
692
     * @param nonViralName
693
     * @return
694
     */
695
    protected List<TaggedText> getSpeciesAggregateTaggedCache(INonViralName nonViralName, boolean addAppended){
696
        if (! isBlank(nonViralName.getAuthorshipCache())){
697
        	List<TaggedText> result = getSpeciesTaggedNameCache(nonViralName, addAppended);
698
        	return result;
699
        }
700

    
701
    	List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
702

    
703
        addSpeciesAggregateTaggedEpithet(tags, nonViralName);
704
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
705
        return tags;
706
    }
707

    
708
    /**
709
     * Adds the aggregate tag to the tag list.
710
     */
711
    private void addSpeciesAggregateTaggedEpithet(List<TaggedText> tags, INonViralName nonViralName) {
712
        String marker;
713
        try {
714
            marker = nonViralName.getRank().getInfraGenericMarker();
715
        } catch (UnknownCdmTypeException e) {
716
            marker = "'unknown aggregat type'";
717
        }
718
        if (isNotBlank(marker)){
719
            tags.add(new TaggedText(TagEnum.rank, marker));
720
        }
721
    }
722

    
723
    /**
724
     * Returns the tag list for a species taxon.
725
     */
726
    protected List<TaggedText> getSpeciesTaggedNameCache(INonViralName nonViralName, boolean addAppended){
727
        List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
728
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
729
        return tags;
730
    }
731

    
732
    protected List<TaggedText> getInfraSpeciesTaggedNameCache(TaxonName name){
733
        if (name.getNameType().isZoological()){
734
            boolean includeMarker = includeInfraSpecificMarkerForZooNames(name);
735
            return getInfraSpeciesTaggedNameCache(name, includeMarker, true);
736
        }else{
737
            return getInfraSpeciesTaggedNameCache(name, true, true);
738
        }
739
    }
740

    
741
    protected boolean includeInfraSpecificMarkerForZooNames(TaxonName name){
742
        return ! (name.isAutonym());  //only exclude marker if autonym, see also ZooNameNoMarkerCacheStrategy
743
    }
744

    
745
    /**
746
     * Creates the tag list for an infraspecific taxon. If include is true the result will contain
747
     * the infraspecific marker (e.g. "var.")
748
     * @param nonViralName
749
     * @param includeMarker
750
     * @return
751
     */
752
    protected List<TaggedText> getInfraSpeciesTaggedNameCache(INonViralName nonViralName,
753
            boolean includeMarker, boolean addAppended){
754
        List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
755
        if (includeMarker || nonViralName.isTrinomHybrid()){
756
            String rankAbbrev = nonViralName.getRank()==null? "" : CdmUtils.Nz(nonViralName.getRank().getAbbreviation()).trim();
757
            String marker = rankAbbrev.replace("null", "");   //TODO really needed?
758
            if (nonViralName.isTrinomHybrid()){
759
                marker = CdmUtils.concat("", NOTHO, marker);
760
            }
761
            if (isNotBlank(marker)){
762
                tags.add(new TaggedText(TagEnum.rank, marker));
763
            }
764

    
765
        }
766
        String infrSpecEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
767

    
768
        infrSpecEpi = infrSpecEpi.trim().replace("null", "");
769

    
770
        if (isNotBlank(infrSpecEpi)){
771
            tags.add(new TaggedText(TagEnum.name, infrSpecEpi));
772
        }
773

    
774
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
775
        return tags;
776
    }
777

    
778

    
779
    /**
780
     * Adds a tag for the hybrid sign and an empty separator to avoid trailing whitespaces.
781
     * @param tags
782
     */
783
    private void addHybridPrefix(List<TaggedText> tags) {
784
        tags.add(new TaggedText(TagEnum.hybridSign, NonViralNameParserImplRegExBase.hybridSign));
785
        tags.add(new TaggedText(TagEnum.separator, "")); //no whitespace separator
786
    }
787

    
788
    /**
789
     * Creates the tag list for the genus and species part.
790
     * @param nonViralName
791
     * @return
792
     */
793
    private List<TaggedText> getGenusAndSpeciesTaggedPart(INonViralName nonViralName) {
794
        //Uninomial
795
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
796

    
797
        //InfraGenericEpi
798
        boolean hasInfraGenericEpi = isNotBlank(nonViralName.getInfraGenericEpithet());
799
        if (hasInfraGenericEpi){
800
            String infrGenEpi = nonViralName.getInfraGenericEpithet().trim();
801
            if (nonViralName.isBinomHybrid()){
802
                //maybe not correct but not really expected to happen
803
                infrGenEpi = UTF8.HYBRID + infrGenEpi;
804
            }
805
            infrGenEpi = "(" + infrGenEpi + ")";
806
            tags.add(new TaggedText(TagEnum.name, infrGenEpi));
807
        }
808

    
809
        //Species Epi
810
        String specEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
811
        if (! hasInfraGenericEpi && nonViralName.isBinomHybrid() ||
812
                hasInfraGenericEpi && nonViralName.isTrinomHybrid()){
813
            addHybridPrefix(tags);
814
        }
815
        if (isNotBlank(specEpi)){
816
            tags.add(new TaggedText(TagEnum.name, specEpi));
817
        }
818
        return tags;
819
    }
820

    
821
    /**
822
     * Adds the tag for the appended phrase if an appended phrase exists
823
     * @param tags
824
     * @param nonViralName
825
     * @param addAppended
826
     */
827
    protected void addAppendedTaggedPhrase(List<TaggedText> tags, INonViralName nonViralName, boolean addAppended){
828
        if (!addAppended){
829
            return;
830
        }
831
        String appendedPhrase = nonViralName ==null ? null : nonViralName.getAppendedPhrase();
832
        if (isNotBlank(appendedPhrase)){
833
            tags.add(new TaggedText(TagEnum.name, appendedPhrase));
834
        }
835
    }
836

    
837
	@Override
838
    public String getLastEpithet(TaxonName taxonName) {
839
	    Rank rank = taxonName.getRank();
840
        //TODO potential NPE / used?
841
	    if(rank.isGenus() || rank.isSupraGeneric()) {
842
            return taxonName.getGenusOrUninomial();
843
        } else if(rank.isInfraGeneric()) {
844
            return taxonName.getInfraGenericEpithet();
845
        } else if(rank.isSpecies()) {
846
            return taxonName.getSpecificEpithet();
847
        } else {
848
            return taxonName.getInfraSpecificEpithet();
849
        }
850
    }
851
}
(6-6/7)