Merge branch 'release/5.18.0'
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / format / taxon / TaxonRelationshipFormatter.java
1 /**
2 * Copyright (C) 2018 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.format.taxon;
10
11 import java.util.ArrayList;
12 import java.util.List;
13
14 import org.apache.commons.lang3.StringUtils;
15
16 import eu.etaxonomy.cdm.common.CdmUtils;
17 import eu.etaxonomy.cdm.common.UTF8;
18 import eu.etaxonomy.cdm.model.agent.Person;
19 import eu.etaxonomy.cdm.model.agent.Team;
20 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
21 import eu.etaxonomy.cdm.model.common.CdmBase;
22 import eu.etaxonomy.cdm.model.common.Language;
23 import eu.etaxonomy.cdm.model.name.TaxonName;
24 import eu.etaxonomy.cdm.model.reference.Reference;
25 import eu.etaxonomy.cdm.model.taxon.Taxon;
26 import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
27 import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
28 import eu.etaxonomy.cdm.model.term.Representation;
29 import eu.etaxonomy.cdm.ref.TypedEntityReference;
30 import eu.etaxonomy.cdm.strategy.cache.TagEnum;
31 import eu.etaxonomy.cdm.strategy.cache.TaggedText;
32 import eu.etaxonomy.cdm.strategy.cache.TaggedTextBuilder;
33 import eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy;
34
35 /**
36 * Formatter for TaxonRelationships.
37 *
38 * @author a.mueller
39 * @since 13.08.2018
40 */
41 public class TaxonRelationshipFormatter {
42
43 private static final String DOUBTFUL_TAXON_MARKER = "?" + UTF8.NARROW_NO_BREAK;
44 private static final String REL_SEC = ", rel. sec. ";
45 private static final String ERR_SEC = ", err. sec. ";
46 private static final String SYN_SEC = ", syn. sec. ";
47 private static final String UNKNOWN_SEC = "???";
48 private static final String NON_SEPARATOR = ", non ";
49 private static final String QUOTE_START = "\""; //TODO
50 private static final String QUOTE_END = "\""; //TODO
51 private static final String AUCT = "auct.";
52 private static final String SENSU_SEPARATOR = " sensu ";
53 private static final String SEC_SEPARATOR = " sec. ";
54 private static final String DETAIL_SEPARATOR = ": ";
55 private static final String INVERT_SYMBOL = "<-"; //TODO
56 private static final String UNDEFINED_SYMBOL = "??"; //TODO
57
58 public List<TaggedText> getTaggedText(TaxonRelationship taxonRelationship, boolean reverse, List<Language> languages) {
59 return getTaggedText(taxonRelationship, reverse, languages, false);
60 }
61
62 public List<TaggedText> getTaggedText(TaxonRelationship taxonRelationship, boolean reverse,
63 List<Language> languages, boolean withoutName) {
64
65 if (taxonRelationship == null){
66 return null;
67 }
68
69 TaxonRelationshipType type = taxonRelationship.getType();
70 boolean isMisapplied = type == null ? false : type.isMisappliedName() && reverse;
71 boolean isSynonym = type == null? false : type.isAnySynonym();
72
73 Taxon relatedTaxon = reverse? taxonRelationship.getFromTaxon()
74 : taxonRelationship.getToTaxon();
75
76 if (relatedTaxon == null){
77 return null;
78 }
79
80 String doubtfulTaxonStr = relatedTaxon.isDoubtful() ? DOUBTFUL_TAXON_MARKER : "";
81 String doubtfulRelationStr = taxonRelationship.isDoubtful() ? "?" : "";
82
83 TaxonName name = relatedTaxon.getName();
84
85 TaggedTextBuilder builder = new TaggedTextBuilder();
86
87 //rel symbol
88 String symbol = doubtfulRelationStr + getSymbol(type, reverse, languages);
89 builder.add(TagEnum.symbol, symbol);
90
91 //name
92 if (!withoutName){
93 if (isMisapplied){
94 //starting quote
95 String startQuote = " " + doubtfulTaxonStr + QUOTE_START;
96 builder.addSeparator(startQuote);
97
98 //name cache
99 List<TaggedText> nameCacheTags = getNameCacheTags(name);
100 builder.addAll(nameCacheTags);
101
102 //end quote
103 String endQuote = QUOTE_END;
104 builder.add(TagEnum.postSeparator, endQuote);
105 }else{
106 builder.addSeparator(" " + doubtfulTaxonStr);
107 //name full title cache
108 List<TaggedText> nameCacheTags = getNameTitleCacheTags(name);
109 builder.addAll(nameCacheTags);
110 }
111 }else{
112 if (isNotBlank(doubtfulTaxonStr)){
113 builder.addSeparator(" " + doubtfulTaxonStr);
114 }
115 }
116
117 //sec/sensu (+ Separatoren?)
118 if (isNotBlank(relatedTaxon.getAppendedPhrase())){
119 builder.addWhitespace();
120 builder.add(TagEnum.appendedPhrase, relatedTaxon.getAppendedPhrase());
121 }
122 List<TaggedText> secTags = getReferenceTags(relatedTaxon.getSec(), relatedTaxon.getSecMicroReference(),
123 /* isMisapplied,*/ false);
124 if (!secTags.isEmpty()) {
125 builder.addSeparator(isMisapplied? SENSU_SEPARATOR : SEC_SEPARATOR);
126 builder.addAll(secTags);
127 }else if (isBlank(relatedTaxon.getAppendedPhrase())) {
128 if (isMisapplied){
129 builder.addWhitespace();
130 //TODO type unclear sensuReference(?)
131 builder.add(TagEnum.appendedPhrase, AUCT);
132 }else{
133 builder.addSeparator(SEC_SEPARATOR + UNKNOWN_SEC);
134 }
135 }
136
137 // //, non author
138 if (isMisapplied && name != null){
139 if (isNotBlank(name.getAuthorshipCache())){
140 builder.addSeparator(NON_SEPARATOR);
141 builder.add(TagEnum.authors, name.getAuthorshipCache().trim());
142 }
143 }
144
145 List<TaggedText> relSecTags = getReferenceTags(taxonRelationship.getCitation(),
146 taxonRelationship.getCitationMicroReference(),true);
147 if (!relSecTags.isEmpty()){
148 builder.addSeparator(isSynonym ? SYN_SEC : isMisapplied ? ERR_SEC : REL_SEC);
149 builder.addAll(relSecTags);
150 }
151
152 return builder.getTaggedText();
153 }
154
155 private List<TaggedText> getReferenceTags(Reference ref, String detail, /*boolean isSensu,*/ boolean isRelation) {
156 List<TaggedText> result = new ArrayList<>();
157 String secRef;
158
159 if (ref != null){
160 TeamOrPersonBase<?> author = ref.getAuthorship();
161 //TODO distinguish linked and unlinked usage,
162 // if reference is not linked short citation should only be used
163 // if both author and year exists, also initials should be added in this case
164 //
165 if (ref.isProtectedTitleCache() == false &&
166 author != null &&
167 isNotBlank(author.getTitleCache())){
168 //TODO move to authorFormatter
169 String familyNames = getFamilyNames(author);
170 if (isNotBlank(familyNames)){
171 secRef = familyNames;
172 }else{
173 secRef = ref.getAuthorship().getTitleCache();
174 }
175 if (isNotBlank(ref.getYear())){
176 secRef += " " + ref.getYear();
177 }
178 }else{
179 secRef = ref.getTitleCache();
180 }
181 TagEnum secType = /*isSensu? TagEnum.sensuReference : */ isRelation? TagEnum.relSecReference : TagEnum.secReference;
182 TaggedText refTag = TaggedText.NewInstance(secType, secRef);
183 refTag.setEntityReference(new TypedEntityReference<>(CdmBase.deproxy(ref).getClass(), ref.getUuid()));
184 result.add(refTag);
185 }
186 if (isNotBlank(detail)){
187 result.add(TaggedText.NewSeparatorInstance(DETAIL_SEPARATOR));
188 TagEnum detailType = /*isSensu? TagEnum.sensuMicroReference : */ isRelation? TagEnum.relSecMicroReference :TagEnum.secMicroReference;
189 TaggedText microTag = TaggedText.NewInstance(detailType, detail);
190 result.add(microTag);
191 }
192 return result;
193 }
194
195 private String getFamilyNames(TeamOrPersonBase<?> author) {
196 if (author.isInstanceOf(Person.class)){
197 Person person = CdmBase.deproxy(author, Person.class);
198 return isNotBlank(person.getFamilyName())? person.getFamilyName() : null;
199 }else{
200 Team team = CdmBase.deproxy(author, Team.class);
201 String result = null;
202 int n = team.getTeamMembers().size();
203 int index = 1;
204 if (team.isHasMoreMembers()){
205 n++;
206 }
207 for (Person member : team.getTeamMembers()){
208 String name = isNotBlank(member.getFamilyName())? member.getFamilyName(): member.getTitleCache();
209 String separator = index < n ? TeamDefaultCacheStrategy.STD_TEAM_CONCATINATION : TeamDefaultCacheStrategy.FINAL_TEAM_CONCATINATION;
210 result = CdmUtils.concat(separator, result, name);
211 index++;
212 }
213 if (team.isHasMoreMembers()){
214 //TODO or et al.???
215 result += TeamDefaultCacheStrategy.ET_AL_TEAM_CONCATINATION_FULL + "al.";
216 }
217 return result;
218 }
219 }
220
221 private List<TaggedText> getNameCacheTags(TaxonName name) {
222 List<TaggedText> result = name.getCacheStrategy().getTaggedName(name);
223 return result;
224 }
225
226 private List<TaggedText> getNameTitleCacheTags(TaxonName name) {
227
228 //TODO full title?
229 List<TaggedText> result = name.getCacheStrategy().getTaggedFullTitle(name);
230 return result;
231 }
232
233 /**
234 * @param type the taxon relationship type
235 * @param reverse is the relationship used reverse
236 * @param languages list of preferred languages
237 * @return the symbol for the taxon relationship
238 */
239 private String getSymbol(TaxonRelationshipType type, boolean reverse, List<Language> languages) {
240 if (type == null){
241 return UNDEFINED_SYMBOL;
242 }
243
244 //symbol
245 String symbol = reverse? type.getInverseSymbol():type.getSymbol();
246 if (isNotBlank(symbol)){
247 return symbol;
248 }
249
250 boolean isSymmetric = type.isSymmetric();
251 //symmetric inverted symbol
252 String invertedSymbol = reverse? type.getSymbol() : type.getInverseSymbol();
253 if (isSymmetric && isNotBlank(invertedSymbol)){
254 return invertedSymbol;
255 }
256
257 //abbrev label
258 Representation representation = reverse? type.getPreferredRepresentation(languages): type.getPreferredInverseRepresentation(languages);
259 String abbrevLabel = representation.getAbbreviatedLabel();
260 if (isNotBlank(abbrevLabel)){
261 return abbrevLabel;
262 }
263
264 //symmetric inverted abbrev label
265 Representation invertedRepresentation = reverse? type.getPreferredInverseRepresentation(languages):type.getPreferredRepresentation(languages);
266 String invertedAbbrevLabel = invertedRepresentation.getAbbreviatedLabel();
267 if (isSymmetric && isNotBlank(invertedAbbrevLabel)){
268 return invertedAbbrevLabel;
269 }
270
271 //non symmetric inverted symbol
272 if (!isSymmetric && isNotBlank(invertedSymbol)){
273 return INVERT_SYMBOL + invertedSymbol;
274 }
275
276 //non symmetric inverted abbrev label
277 if (!isSymmetric && isNotBlank(invertedAbbrevLabel)){
278 return INVERT_SYMBOL + invertedAbbrevLabel;
279 }
280
281 return UNDEFINED_SYMBOL;
282 }
283
284 private boolean isNotBlank(String str) {
285 return StringUtils.isNotBlank(str);
286 }
287
288 private boolean isBlank(String str) {
289 return StringUtils.isBlank(str);
290 }
291 }