Project

General

Profile

Download (31.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;import org.apache.logging.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 = LogManager.getLogger(TaxonNameDefaultCacheStrategy.class);
47
	private static final long serialVersionUID = -6577757501563212669L;
48

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

    
51
    protected String nameAuthorSeperator = " ";
52
    protected String basionymStart = "(";
53
    protected String basionymEnd = ")";
54
    protected String exAuthorSeperator = " ex ";
55
    private static String NOTHO = "notho";
56
    protected CharSequence basionymAuthorCombinationAuthorSeperator = " ";
57

    
58
    protected String zooAuthorYearSeperator = ", ";
59

    
60
    private String cultivarStart = "'";
61
    private String cultivarEnd = "'";
62

    
63
    @Override
64
    public UUID getUuid(){
65
        return uuid;
66
    }
67

    
68
// ************************** FACTORY  ******************************/
69

    
70
    public static TaxonNameDefaultCacheStrategy NewInstance(){
71
        return new TaxonNameDefaultCacheStrategy();
72
    }
73

    
74

    
75
// ************ CONSTRUCTOR *******************/
76

    
77
    protected TaxonNameDefaultCacheStrategy(){
78
        super();
79
    }
80

    
81
/* **************** GETTER / SETTER **************************************/
82

    
83
    /**
84
     * String that separates the NameCache part from the AuthorCache part
85
     * @return
86
     */
87
    public String getNameAuthorSeperator() {
88
        return nameAuthorSeperator;
89
    }
90
    public void setNameAuthorSeperator(String nameAuthorSeperator) {
91
        this.nameAuthorSeperator = nameAuthorSeperator;
92
    }
93

    
94

    
95
    /**
96
     * String the basionym author part starts with e.g. '('.
97
     * This should correspond with the {@link TaxonNameDefaultCacheStrategy#getBasionymEnd() basionymEnd} attribute
98
     * @return
99
     */
100
    public String getBasionymStart() {
101
        return basionymStart;
102
    }
103
    public void setBasionymStart(String basionymStart) {
104
        this.basionymStart = basionymStart;
105
    }
106

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

    
119

    
120
    /**
121
     * String to separate ex author from author.
122
     * @return
123
     */
124
    public String getExAuthorSeperator() {
125
        return exAuthorSeperator;
126
    }
127
    public void setExAuthorSeperator(String exAuthorSeperator) {
128
        this.exAuthorSeperator = exAuthorSeperator;
129
    }
130

    
131

    
132
    /**
133
     * String that separates the basionym/original_combination author part from the combination author part
134
     * @return
135
     */
136
    public CharSequence getBasionymAuthorCombinationAuthorSeperator() {
137
        return basionymAuthorCombinationAuthorSeperator;
138
    }
139

    
140

    
141
    public void setBasionymAuthorCombinationAuthorSeperator( CharSequence basionymAuthorCombinationAuthorSeperator) {
142
        this.basionymAuthorCombinationAuthorSeperator = basionymAuthorCombinationAuthorSeperator;
143
    }
144

    
145
    public String getZooAuthorYearSeperator() {
146
        return zooAuthorYearSeperator;
147
    }
148
    public void setZooAuthorYearSeperator(String authorYearSeperator) {
149
        this.zooAuthorYearSeperator = authorYearSeperator;
150
    }
151

    
152
// ******************* Authorship ******************************/
153

    
154
    @Override
155
    public String getAuthorshipCache(TaxonName taxonName) {
156
        if (taxonName == null){
157
            return null;
158
        }else if (taxonName.isViral()){
159
            return null;
160
        }else if(taxonName.isProtectedAuthorshipCache() == true) {
161
            //cache protected
162
            return taxonName.getAuthorshipCache();
163
        }else{
164
            return getNonCacheAuthorshipCache(taxonName);
165
        }
166
    }
167

    
168
    /**
169
     * Returns the authorshipcache string for the atomized authorship fields. Does not use the authorship field.
170
     * @throws NullPointerException if nonViralName is null.
171
     * @param taxonName
172
     * @return
173
     */
174
    protected String getNonCacheAuthorshipCache(TaxonName nonViralName){
175
        if (nonViralName.getNameType().isZoological()){
176
            return this.getZoologicalNonCacheAuthorshipCache(nonViralName);
177
        }else{
178
            String result = "";
179
            INomenclaturalAuthor combinationAuthor = nonViralName.getCombinationAuthorship();
180
            INomenclaturalAuthor exCombinationAuthor = nonViralName.getExCombinationAuthorship();
181
            INomenclaturalAuthor basionymAuthor = nonViralName.getBasionymAuthorship();
182
            INomenclaturalAuthor exBasionymAuthor = nonViralName.getExBasionymAuthorship();
183
            if (isCultivar(nonViralName) ){
184
                exCombinationAuthor = null;
185
                basionymAuthor = null;
186
                exBasionymAuthor = null;
187
            }
188

    
189
            String basionymPart = "";
190
            String authorPart = "";
191
            //basionym
192
            if (basionymAuthor != null || exBasionymAuthor != null){
193
                basionymPart = basionymStart + getAuthorAndExAuthor(basionymAuthor, exBasionymAuthor) + basionymEnd;
194
            }
195
            if (combinationAuthor != null || exCombinationAuthor != null){
196
                authorPart = getAuthorAndExAuthor(combinationAuthor, exCombinationAuthor);
197
            }
198
            result = CdmUtils.concat(basionymAuthorCombinationAuthorSeperator, basionymPart, authorPart);
199
//        if ("".equals(result)){
200
//        	result = null;
201
//        }
202
            return result;
203
        }
204
    }
205

    
206
    private boolean isCultivar(TaxonName name) {
207
        return name.isCultivar() || isNotBlank(name.getCultivarEpithet()) || isNotBlank(name.getCultivarGroupEpithet());
208
    }
209

    
210
    protected String getZoologicalNonCacheAuthorshipCache(TaxonName nonViralName) {
211
        if (nonViralName == null){
212
            return null;
213
        }
214
        String result = "";
215
        INomenclaturalAuthor combinationAuthor = nonViralName.getCombinationAuthorship();
216
        INomenclaturalAuthor exCombinationAuthor = nonViralName.getExCombinationAuthorship();
217
        INomenclaturalAuthor basionymAuthor = nonViralName.getBasionymAuthorship();
218
        INomenclaturalAuthor exBasionymAuthor = nonViralName.getExBasionymAuthorship();
219
        Integer publicationYear = nonViralName.getPublicationYear();
220
        Integer originalPublicationYear = nonViralName.getOriginalPublicationYear();
221

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

    
243

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

    
264
    private String getNomAuthorTitle(INomenclaturalAuthor author) {
265
        return CdmUtils.Nz(author.getNomenclaturalTitleCache());
266
    }
267

    
268
    /**
269
     * Checks if the given name should include the author in it's cached version.<BR>
270
     * This is usually the case but not for <i>species aggregates</i>.
271
     * @param nonViralName
272
     * @return
273
     */
274
    protected boolean nameIncludesAuthorship(INonViralName nonViralName){
275
        Rank rank = nonViralName.getRank();
276
        if (rank != null && rank.isSpeciesAggregate()){
277
            return false;
278
        }else{
279
            return true;
280
        }
281
    }
282

    
283
// ************* TAGGED NAME ***************************************/
284

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

    
322
    /**
323
     * Returns the tag list of the name part (without author and reference).
324
     * @param taxonName
325
     * @return
326
     */
327
    @Override
328
    public List<TaggedText> getTaggedName(TaxonName taxonName) {
329
        if (taxonName == null){
330
            return null;
331
        }else if (taxonName.isViral()){
332
            return null;
333
        }
334
        List<TaggedText> tags = new ArrayList<>();
335
        Rank rank = taxonName.getRank();
336

    
337
        if (taxonName.isProtectedNameCache()){
338
            tags.add(new TaggedText(TagEnum.name, taxonName.getNameCache()));
339
        }else if (taxonName.isHybridFormula()){
340
            //hybrid formula
341
            //TODO graft-chimera (see also cultivars)
342
            String hybridSeparator = NonViralNameParserImplRegExBase.hybridSign;
343
            boolean isFirst = true;
344
            List<HybridRelationship> rels = taxonName.getOrderedChildRelationships();
345
            for (HybridRelationship rel: rels){
346
                if (! isFirst){
347
                    tags.add(new TaggedText(TagEnum.hybridSign, hybridSeparator));
348
                }
349
                isFirst = false;
350
                tags.addAll(getTaggedName(rel.getParentName()));
351
            }
352
            return tags;
353

    
354
        }else if (rank == null){
355
            tags = getRanklessTaggedNameCache(taxonName, true);
356
		}else if (rank.isCultivar()){
357
			tags = getCultivarTaggedNameCache(taxonName);
358
        }else if (rank.isInfraSpecific()){
359
            tags = getInfraSpeciesTaggedNameCache(taxonName);
360
        }else if (rank.isSpecies() || isAggregateWithAuthorship(taxonName, rank) ){ //exception see #4288
361
            tags = getSpeciesTaggedNameCache(taxonName, true);
362
        }else if (rank.isInfraGeneric()){
363
            tags = getInfraGenusTaggedNameCache(taxonName, true);
364
        }else if (rank.isGenus()){
365
            tags = getGenusOrUninomialTaggedNameCache(taxonName, true);
366
        }else if (rank.isSupraGeneric()){
367
            tags = getGenusOrUninomialTaggedNameCache(taxonName, true);
368
        }else{
369
            tags = getRanklessTaggedNameCache(taxonName, true);
370
            logger.warn("Rank does not belong to a rank class: " + rank.getTitleCache() + ". Used rankless nameCache for name " + taxonName.getUuid());
371
        }
372
        //TODO handle appended phrase here instead of different places, check first if this is true for all
373
        //cases
374

    
375
        return tags;
376
    }
377

    
378
//***************************** PRIVATES ***************************************/
379

    
380
    private List<TaggedText> getCultivarTaggedNameCache(TaxonName taxonName) {
381
        List<TaggedText> scientificNameTags;
382
        TaggedTextBuilder builder = TaggedTextBuilder.NewInstance();
383
        if (isNotBlank(taxonName.getInfraSpecificEpithet())){
384
            scientificNameTags = getInfraSpeciesTaggedNameCache(taxonName, false, false);
385
        } else if (isNotBlank(taxonName.getSpecificEpithet())){
386
            scientificNameTags = getSpeciesTaggedNameCache(taxonName, false);
387
        } else if (isNotBlank(taxonName.getInfraGenericEpithet())){
388
            scientificNameTags = getInfraGenusTaggedNameCache(taxonName, false);
389
        } else /*if (isNotBlank(taxonName.getGenusOrUninomial())) */{
390
            scientificNameTags = getGenusOrUninomialTaggedNameCache(taxonName, false);
391
        }
392

    
393
        UUID rankUuid = taxonName.getRank().getUuid();
394
        boolean rankIsHandled = true;
395
        String cultivarStr = null;
396
        String groupStr = taxonName.getCultivarGroupEpithet();
397
        if (rankUuid.equals(Rank.uuidCultivar)){
398
            cultivarStr = surroundedCultivarEpithet(taxonName.getCultivarEpithet());
399
            if (isNotBlank(cultivarStr) && isNotBlank(groupStr)){
400
                groupStr = surroundGroupWithBracket(groupStr);
401
            }
402
            cultivarStr = CdmUtils.concat(" ", groupStr, cultivarStr);
403
        }else if (rankUuid.equals(Rank.uuidCultivarGroup)){
404
            cultivarStr = CdmUtils.concat(" ", groupStr, checkHasGroupEpithet(groupStr)? null: "Group");
405
        }else if (rankUuid.equals(Rank.uuidGrexICNCP)){
406
            cultivarStr = CdmUtils.concat(" ", groupStr, checkHasGrexEpithet(groupStr)? null: "grex");
407
        }else{
408
            rankIsHandled = false;
409
        }
410
        if (rankIsHandled){
411
            builder.addAll(scientificNameTags);
412
        }else if (rankUuid.equals(Rank.uuidGraftChimaera)){
413
            //TODO not yet fully implemented
414
            cultivarStr = "+ " + CdmUtils.concat(" ", taxonName.getGenusOrUninomial(), surroundedCultivarEpithet(taxonName.getCultivarEpithet()));
415
        }else if (rankUuid.equals(Rank.uuidDenominationClass)){
416
            //TODO dummy implementation
417
            cultivarStr = CdmUtils.concat(" ", taxonName.getGenusOrUninomial(), surroundedCultivarEpithet(taxonName.getCultivarEpithet()));
418
        } else { //(!rankIsHandled)
419
            throw new IllegalStateException("Unsupported rank " + taxonName.getRank().getTitleCache() + " for cultivar.");
420
        }
421
        if (isNotBlank(cultivarStr)){
422
            builder.add(TagEnum.cultivar, cultivarStr);
423
        }
424

    
425
        List<TaggedText> tags = builder.getTaggedText();
426
        addAppendedTaggedPhrase(tags, taxonName, true);
427
        return tags;
428
    }
429

    
430
    private String surroundGroupWithBracket(String groupStr) {
431
        if (groupStr.matches(NonViralNameParserImplRegExBase.grex + "$")){
432
            return groupStr;
433
        }else if (groupStr.matches(".*" + NonViralNameParserImplRegExBase.grex + ".+")){
434
            Matcher matcher = Pattern.compile(NonViralNameParserImplRegExBase.grex + "\\s*" ).matcher(groupStr);
435
            matcher.find();
436
            return groupStr.substring(0, matcher.end()) + "("+ groupStr.substring(matcher.end())+ ")";
437
        }else{
438
            return "("+ groupStr + ")";
439
        }
440
    }
441

    
442
    private boolean checkHasGroupEpithet(String group) {
443

    
444
        String[] splits = group == null? new String[0]: group.split("\\s+");
445
        if (splits.length <= 1){
446
            return false;
447
        }else if (splits[0].matches(NonViralNameParserImplRegExBase.group)
448
                || splits[splits.length-1].matches(NonViralNameParserImplRegExBase.group)){
449
            return true;
450
        }else{
451
            return false;
452
        }
453
    }
454

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

    
466
    private String surroundedCultivarEpithet(String cultivarEpi) {
467
        return cultivarStart + CdmUtils.Nz(cultivarEpi) + cultivarEnd;
468
    }
469

    
470
    private boolean isAggregateWithAuthorship(TaxonName nonViralName, Rank rank) {
471
		if (rank == null){
472
			return false;
473
		}else{
474
			return rank.isSpeciesAggregate() && ( isNotBlank(nonViralName.getAuthorshipCache()) || nonViralName.getNomenclaturalReference() != null );
475
		}
476
	}
477

    
478
	/**
479
     * Returns the tag list for an autonym taxon.
480
     *
481
     * @see NonViralName#isAutonym()
482
     * @see BotanicalName#isAutonym()
483
     * @param nonViralName
484
     * @return
485
     */
486
    private List<TaggedText> handleTaggedAutonym(TaxonName nonViralName, boolean addAppended) {
487
    	List<TaggedText> tags = null;
488
    	if (nonViralName.isInfraSpecific()){
489
	        //species part
490
	        tags = getSpeciesTaggedNameCache(nonViralName, addAppended);
491

    
492
	        //author
493
	        String authorCache = getAuthorshipCache(nonViralName);
494
	        if (isNotBlank(authorCache)){
495
	            tags.add(new TaggedText(TagEnum.authors, authorCache));
496
	        }
497

    
498
	        //infra species marker
499
	        if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraSpecific()){
500
	            //TODO handle exception
501
	            logger.warn("Rank for autonym does not exist or is not lower than species !!");
502
	        }else{
503
	            String infraSpeciesMarker = nonViralName.getRank().getAbbreviation();
504
	            if (nonViralName.isTrinomHybrid()){
505
	                infraSpeciesMarker = CdmUtils.concat("", NOTHO, infraSpeciesMarker);
506
	            }
507
	            if (isNotBlank(infraSpeciesMarker)){
508
	                tags.add(new TaggedText(TagEnum.rank, infraSpeciesMarker));
509
	            }
510
	        }
511

    
512
	        //infra species
513
	        String infraSpeciesPart = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet()).trim();
514
	        if (isNotBlank(infraSpeciesPart)){
515
	            tags.add(new TaggedText(TagEnum.name, infraSpeciesPart));
516
	        }
517

    
518
        } else if (nonViralName.isInfraGeneric()){
519
        	//genus part
520
	       tags =getGenusOrUninomialTaggedNameCache(nonViralName, addAppended);
521

    
522
	       //author
523
           String authorCache = getAuthorshipCache(nonViralName);
524
           if (isNotBlank(authorCache)){
525
               tags.add(new TaggedText(TagEnum.authors, authorCache));
526
           }
527

    
528
	        //infra species marker
529
	        if (nonViralName.getRank() == null || !nonViralName.getRank().isInfraGeneric()){
530
	            //TODO handle exception
531
	            logger.warn("Rank for autonym does not exist or is not lower than species !!");
532
	        }else{
533
	        	Rank rank = nonViralName.getRank();
534
	            String infraGenericMarker = rank.getAbbreviation();
535
                if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
536
                	infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
537
                }
538
	            if (isNotBlank(infraGenericMarker)){
539
	                tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
540
	            }
541
	        }
542

    
543
	        //infra genus
544
	        String infraGenericPart = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
545
	        if (isNotBlank(infraGenericPart)){
546
	            tags.add(new TaggedText(TagEnum.name, infraGenericPart));
547
	        }
548
        }
549

    
550
        return tags;
551
    }
552

    
553

    
554

    
555
    /**
556
     * Returns the tag list for rankless taxa.
557
     * @param nonViralName
558
     * @return
559
     */
560
    protected List<TaggedText> getRanklessTaggedNameCache(INonViralName nonViralName, boolean addAppended){
561
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
562
        String speciesEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
563
        if (isNotBlank(speciesEpi)){
564
            tags.add(new TaggedText(TagEnum.name, speciesEpi));
565
        }
566

    
567
        String infraSpeciesEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
568
        if (isNotBlank(infraSpeciesEpi)){
569
            tags.add(new TaggedText(TagEnum.name, infraSpeciesEpi));
570
        }
571

    
572
        //result += " (rankless)";
573
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
574
        return tags;
575
    }
576

    
577
    /**
578
     * Returns the tag list for the first epithet (including a hybrid sign if required).
579
     * @param nonViralName
580
     * @return
581
     */
582
    private List<TaggedText> getUninomialTaggedPart(INonViralName nonViralName) {
583
        List<TaggedText> tags = new ArrayList<>();
584

    
585
        if (nonViralName.isMonomHybrid()){
586
            addHybridPrefix(tags);
587
        }
588

    
589
        String uninomial = CdmUtils.Nz(nonViralName.getGenusOrUninomial()).trim();
590
        if (isNotBlank(uninomial)){
591
            tags.add(new TaggedText(TagEnum.name, uninomial));
592
        }
593

    
594
        return tags;
595
    }
596

    
597
    /**
598
     * Returns the tag list for an genus or higher taxon.
599
     *
600
     * @param nonViralName
601
     * @return
602
     */
603
    protected List<TaggedText> getGenusOrUninomialTaggedNameCache(INonViralName nonViralName, boolean addAppended){
604
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
605
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
606
        return tags;
607
    }
608

    
609
    /**
610
     * Returns the tag list for an infrageneric taxon (including species aggregates).
611
     *
612
     * @see #getSpeciesAggregateTaggedCache(NonViralName)
613
     * @param nonViralName
614
     * @return
615
     */
616
    protected List<TaggedText> getInfraGenusTaggedNameCache(INonViralName nonViralName, boolean addAppended){
617
        Rank rank = nonViralName.getRank();
618
        if (rank != null && rank.isSpeciesAggregate() && isBlank(nonViralName.getAuthorshipCache())){
619
            return getSpeciesAggregateTaggedCache(nonViralName, addAppended);
620
        }
621

    
622
        //genus
623
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
624

    
625
        //marker
626
        String infraGenericMarker;
627
        if (rank != null){
628
            try {
629
                infraGenericMarker = rank.getInfraGenericMarker();
630
                if (rank.equals(Rank.SECTION_BOTANY()) || rank.equals(Rank.SUBSECTION_BOTANY())){
631
                	infraGenericMarker = infraGenericMarker.replace("(bot.)", "");
632
                }
633
            } catch (UnknownCdmTypeException e) {
634
                infraGenericMarker = "'unhandled infrageneric rank'";
635
            }
636
        }else{
637
        	infraGenericMarker = "'undefined infrageneric rank'";
638
        }
639
        String infraGenEpi = CdmUtils.Nz(nonViralName.getInfraGenericEpithet()).trim();
640
        if (nonViralName.isBinomHybrid()){
641
            infraGenericMarker = CdmUtils.concat("", NOTHO, infraGenericMarker);
642
        }
643

    
644
        addInfraGenericPart(nonViralName, tags, infraGenericMarker, infraGenEpi);
645

    
646
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
647
        return tags;
648
    }
649

    
650

    
651
	/**
652
	 * Default implementation for the infrageneric part of a name.
653
	 * This is usually the infrageneric marker and the infrageneric epitheton. But may be
654
	 * implemented differently e.g. for zoological names the infrageneric epitheton
655
	 * may be surrounded by brackets and the marker left out.
656
	 * @param nonViralName
657
	 * @param tags
658
	 * @param infraGenericMarker
659
	 */
660
	protected void addInfraGenericPart(
661
	        @SuppressWarnings("unused") INonViralName name,
662
	        List<TaggedText> tags,
663
	        String infraGenericMarker,
664
	        String infraGenEpi) {
665
		//add marker
666
		tags.add(new TaggedText(TagEnum.rank, infraGenericMarker));
667

    
668
		//add epitheton
669
		if (isNotBlank(infraGenEpi)){
670
            tags.add(new TaggedText(TagEnum.name, infraGenEpi));
671
        }
672
	}
673

    
674
    /**
675
     * Returns the tag list for a species aggregate (or similar) taxon.<BR>
676
     * Possible ranks for a <i>species aggregate</i> are "aggr.", "species group", ...
677
     * @param nonViralName
678
     * @return
679
     */
680
    protected List<TaggedText> getSpeciesAggregateTaggedCache(INonViralName nonViralName, boolean addAppended){
681
        if (! isBlank(nonViralName.getAuthorshipCache())){
682
        	List<TaggedText> result = getSpeciesTaggedNameCache(nonViralName, addAppended);
683
        	return result;
684
        }
685

    
686
    	List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
687

    
688
        addSpeciesAggregateTaggedEpithet(tags, nonViralName);
689
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
690
        return tags;
691
    }
692

    
693
    /**
694
     * Adds the aggregate tag to the tag list.
695
     */
696
    private void addSpeciesAggregateTaggedEpithet(List<TaggedText> tags, INonViralName nonViralName) {
697
        String marker;
698
        try {
699
            marker = nonViralName.getRank().getInfraGenericMarker();
700
        } catch (UnknownCdmTypeException e) {
701
            marker = "'unknown aggregat type'";
702
        }
703
        if (isNotBlank(marker)){
704
            tags.add(new TaggedText(TagEnum.rank, marker));
705
        }
706
    }
707

    
708
    /**
709
     * Returns the tag list for a species taxon.
710
     */
711
    protected List<TaggedText> getSpeciesTaggedNameCache(INonViralName nonViralName, boolean addAppended){
712
        List<TaggedText> tags = getGenusAndSpeciesTaggedPart(nonViralName);
713
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
714
        return tags;
715
    }
716

    
717
    protected List<TaggedText> getInfraSpeciesTaggedNameCache(TaxonName name){
718
        if (name.getNameType().isZoological()){
719
            boolean includeMarker = includeInfraSpecificMarkerForZooNames(name);
720
            return getInfraSpeciesTaggedNameCache(name, includeMarker, true);
721
        }else{
722
            return getInfraSpeciesTaggedNameCache(name, true, true);
723
        }
724
    }
725

    
726
    protected boolean includeInfraSpecificMarkerForZooNames(TaxonName name){
727
        return ! (name.isAutonym());  //only exclude marker if autonym, see also ZooNameNoMarkerCacheStrategy
728
    }
729

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

    
749
        }
750
        String infrSpecEpi = CdmUtils.Nz(nonViralName.getInfraSpecificEpithet());
751

    
752
        infrSpecEpi = infrSpecEpi.trim().replace("null", "");
753

    
754
        if (isNotBlank(infrSpecEpi)){
755
            tags.add(new TaggedText(TagEnum.name, infrSpecEpi));
756
        }
757

    
758
        addAppendedTaggedPhrase(tags, nonViralName, addAppended);
759
        return tags;
760
    }
761

    
762

    
763
    /**
764
     * Adds a tag for the hybrid sign and an empty separator to avoid trailing whitespaces.
765
     * @param tags
766
     */
767
    private void addHybridPrefix(List<TaggedText> tags) {
768
        tags.add(new TaggedText(TagEnum.hybridSign, NonViralNameParserImplRegExBase.hybridSign));
769
        tags.add(new TaggedText(TagEnum.separator, "")); //no whitespace separator
770
    }
771

    
772
    /**
773
     * Creates the tag list for the genus and species part.
774
     * @param nonViralName
775
     * @return
776
     */
777
    private List<TaggedText> getGenusAndSpeciesTaggedPart(INonViralName nonViralName) {
778
        //Uninomial
779
        List<TaggedText> tags = getUninomialTaggedPart(nonViralName);
780

    
781
        //InfraGenericEpi
782
        boolean hasInfraGenericEpi = isNotBlank(nonViralName.getInfraGenericEpithet());
783
        if (hasInfraGenericEpi){
784
            String infrGenEpi = nonViralName.getInfraGenericEpithet().trim();
785
            if (nonViralName.isBinomHybrid()){
786
                //maybe not correct but not really expected to happen
787
                infrGenEpi = UTF8.HYBRID + infrGenEpi;
788
            }
789
            infrGenEpi = "(" + infrGenEpi + ")";
790
            tags.add(new TaggedText(TagEnum.name, infrGenEpi));
791
        }
792

    
793
        //Species Epi
794
        String specEpi = CdmUtils.Nz(nonViralName.getSpecificEpithet()).trim();
795
        if (! hasInfraGenericEpi && nonViralName.isBinomHybrid() ||
796
                hasInfraGenericEpi && nonViralName.isTrinomHybrid()){
797
            addHybridPrefix(tags);
798
        }
799
        if (isNotBlank(specEpi)){
800
            tags.add(new TaggedText(TagEnum.name, specEpi));
801
        }
802
        return tags;
803
    }
804

    
805
    /**
806
     * Adds the tag for the appended phrase if an appended phrase exists
807
     * @param tags
808
     * @param nonViralName
809
     * @param addAppended
810
     */
811
    protected void addAppendedTaggedPhrase(List<TaggedText> tags, INonViralName nonViralName, boolean addAppended){
812
        if (!addAppended){
813
            return;
814
        }
815
        String appendedPhrase = nonViralName ==null ? null : nonViralName.getAppendedPhrase();
816
        if (isNotBlank(appendedPhrase)){
817
            tags.add(new TaggedText(TagEnum.name, appendedPhrase));
818
        }
819
    }
820

    
821
	@Override
822
    public String getLastEpithet(TaxonName taxonName) {
823
        Rank rank = taxonName.getRank();
824
        if(rank.isGenus() || rank.isSupraGeneric()) {
825
            return taxonName.getGenusOrUninomial();
826
        } else if(rank.isInfraGeneric()) {
827
            return taxonName.getInfraGenericEpithet();
828
        } else if(rank.isSpecies()) {
829
            return taxonName.getSpecificEpithet();
830
        } else {
831
            return taxonName.getInfraSpecificEpithet();
832
        }
833
    }
834

    
835

    
836
}
(6-6/7)