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