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