001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *     http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.gbif.api.model.checklistbank;
015
016import org.gbif.api.model.common.LinneanClassification;
017import org.gbif.api.model.common.LinneanClassificationKeys;
018import org.gbif.api.util.ClassificationUtils;
019import org.gbif.api.vocabulary.Rank;
020import org.gbif.api.vocabulary.TaxonomicStatus;
021
022import java.io.Serializable;
023import java.util.List;
024import java.util.Objects;
025import java.util.StringJoiner;
026
027import javax.annotation.Nullable;
028import javax.validation.constraints.Max;
029import javax.validation.constraints.Min;
030
031import com.fasterxml.jackson.annotation.JsonProperty;
032
033import io.swagger.v3.oas.annotations.media.Schema;
034
035/**
036 * The resulting lookup of a name usage match.
037 * A single name usage key with its Linnean classification and a confidence value for the match.
038 */
039@SuppressWarnings("unused")
040public class NameUsageMatch implements LinneanClassification, LinneanClassificationKeys, Serializable {
041
042  private static final long serialVersionUID = -8927655067465421358L;
043
044  private Integer usageKey;
045  private Integer acceptedUsageKey;
046  private String scientificName;
047  private String canonicalName;
048  private Rank rank;
049  private TaxonomicStatus status; // ACCEPTED, SYNONYM or DOUBTFUL only!
050  private Integer confidence;
051  private String note;
052  private MatchType matchType = MatchType.NONE;
053  private List<NameUsageMatch> alternatives;
054  // for LinneanClassification
055  private String kingdom;
056  private String phylum;
057  @JsonProperty("class")
058  private String clazz;
059  private String order;
060  private String family;
061  private String genus;
062  private String subgenus;
063  private String species;
064  // for LinneanClassificationKeys
065  private Integer kingdomKey;
066  private Integer phylumKey;
067  private Integer classKey;
068  private Integer orderKey;
069  private Integer familyKey;
070  private Integer genusKey;
071  private Integer subgenusKey;
072  private Integer speciesKey;
073
074  /**
075   * The confidence that the lookup was correct.
076   * A value between 0 and 100 with higher values being better matches.
077   *
078   * @return the lookup confidence
079   */
080  @Schema(description = "The confidence that the lookup was correct.\n\n" +
081    "A value between 0 and 100 with higher values being better matches.")
082  @Min(0)
083  @Max(100)
084  public Integer getConfidence() {
085    return confidence;
086  }
087
088  /**
089   * @param confidence the confidence to set
090   */
091  public void setConfidence(Integer confidence) {
092    this.confidence = confidence;
093  }
094
095  /**
096   * @return the type of match for this result
097   */
098  @Schema(description = "The type of match for this result.")
099  public MatchType getMatchType() {
100    return matchType;
101  }
102
103  public void setMatchType(MatchType matchType) {
104    this.matchType = matchType;
105  }
106
107  /**
108   * @return the rank of the matching usage
109   */
110  @Schema(description = "The rank of the matching usage.")
111  @Nullable
112  public Rank getRank() {
113    return rank;
114  }
115
116  public void setRank(Rank rank) {
117    this.rank = rank;
118  }
119
120  /**
121   * The scientific name of the matched name usage.
122   *
123   * @return the scientific name of the matched usage
124   */
125  @Schema(description = "The scientific name of the matched name usage.")
126  @Nullable
127  public String getScientificName() {
128    return scientificName;
129  }
130
131  public void setScientificName(String scientificName) {
132    this.scientificName = scientificName;
133  }
134
135  /**
136   * The name usage key of the name usage that has been matched.
137   *
138   * @return the usageKey
139   */
140  @Schema(description = "The name usage key of the name usage that has been matched.")
141  @Nullable
142  public Integer getUsageKey() {
143    return usageKey;
144  }
145
146  /**
147   * @param usageKey the usageKey to set
148   */
149  public void setUsageKey(Integer usageKey) {
150    this.usageKey = usageKey;
151  }
152
153  /**
154   * The key of the accepted name usage in case the matched usage was a synonym.
155   */
156  @Schema(description = "The key of the accepted name usage in case the matched usage was a synonym.")
157  @Nullable
158  public Integer getAcceptedUsageKey() {
159    return acceptedUsageKey;
160  }
161
162  public void setAcceptedUsageKey(Integer acceptedUsageKey) {
163    this.acceptedUsageKey = acceptedUsageKey;
164  }
165
166  /**
167   * @return true if it's a synonym
168   */
169  @Schema(description = "True if the match name is a synonym.")
170  public boolean isSynonym() {
171    return status != null && status.isSynonym();
172  }
173
174  /**
175   * The taxonomic status of the backbone usage. This field is required and only 3 values are allowed:
176   * <ul>
177   *   <li>accepted: regular accepted taxon</li>
178   *   <li>synonym: any kind of synonym</li>
179   *   <li>doubtful: treated as accepted but in doubt for some reason</li>
180   * </ul>
181   */
182  @Schema(description = "The taxonomic status of the backbone usage.")
183  public TaxonomicStatus getStatus() {
184    return status;
185  }
186
187  public void setStatus(TaxonomicStatus status) {
188    this.status = status;
189  }
190
191  @Schema(description = "Matched name's kingdom.")
192  @Override
193  @Nullable
194  public String getKingdom() {
195    return kingdom;
196  }
197
198  @Override
199  public void setKingdom(String kingdom) {
200    this.kingdom = kingdom;
201  }
202
203  @Schema(description = "Matched name's phylum.")
204  @Override
205  @Nullable
206  public String getPhylum() {
207    return phylum;
208  }
209
210  @Override
211  public void setPhylum(String phylum) {
212    this.phylum = phylum;
213  }
214
215  @Schema(description = "Matched name's class.")
216  @Override
217  @Nullable
218  public String getClazz() {
219    return clazz;
220  }
221
222  @Override
223  public void setClazz(String clazz) {
224    this.clazz = clazz;
225  }
226
227  @Schema(description = "Matched name's order.")
228  @Override
229  @Nullable
230  public String getOrder() {
231    return order;
232  }
233
234  @Override
235  public void setOrder(String order) {
236    this.order = order;
237  }
238
239  @Schema(description = "Matched name's family.")
240  @Override
241  @Nullable
242  public String getFamily() {
243    return family;
244  }
245
246  @Override
247  public void setFamily(String family) {
248    this.family = family;
249  }
250
251  @Schema(description = "Matched name's genus.")
252  @Override
253  @Nullable
254  public String getGenus() {
255    return genus;
256  }
257
258  @Override
259  public void setGenus(String genus) {
260    this.genus = genus;
261  }
262
263  @Schema(description = "Matched name's subgenus.")
264  @Override
265  @Nullable
266  public String getSubgenus() {
267    return subgenus;
268  }
269
270  @Override
271  public void setSubgenus(String subgenus) {
272    this.subgenus = subgenus;
273  }
274
275  @Schema(description = "Matched name's species.")
276  @Override
277  @Nullable
278  public String getSpecies() {
279    return species;
280  }
281
282  @Override
283  public void setSpecies(String species) {
284    this.species = species;
285  }
286
287  @Schema(description = "Usage key of the kingdom of the matched name.")
288  @Override
289  @Nullable
290  public Integer getKingdomKey() {
291    return kingdomKey;
292  }
293
294  @Override
295  public void setKingdomKey(Integer kingdomKey) {
296    this.kingdomKey = kingdomKey;
297  }
298
299  @Schema(description = "Usage key of the phylum of the matched name.")
300  @Override
301  @Nullable
302  public Integer getPhylumKey() {
303    return phylumKey;
304  }
305
306  @Override
307  public void setPhylumKey(Integer phylumKey) {
308    this.phylumKey = phylumKey;
309  }
310
311  @Schema(description = "Usage key of the class of the matched name.")
312  @Override
313  @Nullable
314  public Integer getClassKey() {
315    return classKey;
316  }
317
318  @Override
319  public void setClassKey(Integer classKey) {
320    this.classKey = classKey;
321  }
322
323  @Schema(description = "Usage key of the order of the matched name.")
324  @Override
325  @Nullable
326  public Integer getOrderKey() {
327    return orderKey;
328  }
329
330  @Override
331  public void setOrderKey(Integer orderKey) {
332    this.orderKey = orderKey;
333  }
334
335  @Schema(description = "Usage key of the family of the matched name.")
336  @Override
337  @Nullable
338  public Integer getFamilyKey() {
339    return familyKey;
340  }
341
342  @Override
343  public void setFamilyKey(Integer familyKey) {
344    this.familyKey = familyKey;
345  }
346
347  @Schema(description = "Usage key of the genus of the matched name.")
348  @Override
349  @Nullable
350  public Integer getGenusKey() {
351    return genusKey;
352  }
353
354  @Override
355  public void setGenusKey(Integer genusKey) {
356    this.genusKey = genusKey;
357  }
358
359  @Schema(description = "Usage key of the subgenus of the matched name.")
360  @Override
361  @Nullable
362  public Integer getSubgenusKey() {
363    return subgenusKey;
364  }
365
366  @Override
367  public void setSubgenusKey(Integer subgenusKey) {
368    this.subgenusKey = subgenusKey;
369  }
370
371  @Schema(description = "Usage key of the species of the matched name.")
372  @Override
373  @Nullable
374  public Integer getSpeciesKey() {
375    return speciesKey;
376  }
377
378  @Override
379  public void setSpeciesKey(Integer speciesKey) {
380    this.speciesKey = speciesKey;
381  }
382
383  @Schema(description = "Usage key of the kingdom of the matched name.")
384  @Override
385  @Nullable
386  public String getHigherRank(Rank rank) {
387    return ClassificationUtils.getHigherRank(this, rank);
388  }
389
390  @Override
391  public Integer getHigherRankKey(Rank rank) {
392    return ClassificationUtils.getHigherRankKey(this, rank);
393  }
394
395  @Nullable
396  public String getCanonicalName() {
397    return canonicalName;
398  }
399
400  public void setCanonicalName(String canonicalName) {
401    this.canonicalName = canonicalName;
402  }
403
404  /**
405   * Optional notes on the matching.
406   */
407  @Nullable
408  public String getNote() {
409    return note;
410  }
411
412  public void setNote(String note) {
413    this.note = note;
414  }
415
416  /**
417   * @return a list of alternative matches considered
418   */
419  @Nullable
420  public List<NameUsageMatch> getAlternatives() {
421    return alternatives;
422  }
423
424  public void setAlternatives(List<NameUsageMatch> alternatives) {
425    this.alternatives = alternatives;
426  }
427
428  @Override
429  public boolean equals(Object o) {
430    if (this == o) {
431      return true;
432    }
433    if (o == null || getClass() != o.getClass()) {
434      return false;
435    }
436    NameUsageMatch that = (NameUsageMatch) o;
437    return Objects.equals(usageKey, that.usageKey) &&
438      Objects.equals(acceptedUsageKey, that.acceptedUsageKey) &&
439      Objects.equals(scientificName, that.scientificName) &&
440      Objects.equals(canonicalName, that.canonicalName) &&
441      rank == that.rank &&
442      status == that.status &&
443      Objects.equals(confidence, that.confidence) &&
444      Objects.equals(note, that.note) &&
445      matchType == that.matchType &&
446      Objects.equals(alternatives, that.alternatives) &&
447      Objects.equals(kingdom, that.kingdom) &&
448      Objects.equals(phylum, that.phylum) &&
449      Objects.equals(clazz, that.clazz) &&
450      Objects.equals(order, that.order) &&
451      Objects.equals(family, that.family) &&
452      Objects.equals(genus, that.genus) &&
453      Objects.equals(subgenus, that.subgenus) &&
454      Objects.equals(species, that.species) &&
455      Objects.equals(kingdomKey, that.kingdomKey) &&
456      Objects.equals(phylumKey, that.phylumKey) &&
457      Objects.equals(classKey, that.classKey) &&
458      Objects.equals(orderKey, that.orderKey) &&
459      Objects.equals(familyKey, that.familyKey) &&
460      Objects.equals(genusKey, that.genusKey) &&
461      Objects.equals(subgenusKey, that.subgenusKey) &&
462      Objects.equals(speciesKey, that.speciesKey);
463  }
464
465  @Override
466  public int hashCode() {
467    return Objects
468      .hash(usageKey, acceptedUsageKey, scientificName, canonicalName, rank, status, confidence,
469        note, matchType, alternatives, kingdom, phylum, clazz, order, family, genus, subgenus,
470        species, kingdomKey, phylumKey, classKey, orderKey, familyKey, genusKey, subgenusKey,
471        speciesKey);
472  }
473
474  @Override
475  public String toString() {
476    return new StringJoiner(", ", NameUsageMatch.class.getSimpleName() + "[", "]")
477      .add("usageKey=" + usageKey)
478      .add("acceptedUsageKey=" + acceptedUsageKey)
479      .add("scientificName='" + scientificName + "'")
480      .add("canonicalName='" + canonicalName + "'")
481      .add("rank=" + rank)
482      .add("status=" + status)
483      .add("confidence=" + confidence)
484      .add("note='" + note + "'")
485      .add("matchType=" + matchType)
486      .add("alternatives=" + alternatives)
487      .add("kingdom='" + kingdom + "'")
488      .add("phylum='" + phylum + "'")
489      .add("clazz='" + clazz + "'")
490      .add("order='" + order + "'")
491      .add("family='" + family + "'")
492      .add("genus='" + genus + "'")
493      .add("subgenus='" + subgenus + "'")
494      .add("species='" + species + "'")
495      .add("kingdomKey=" + kingdomKey)
496      .add("phylumKey=" + phylumKey)
497      .add("classKey=" + classKey)
498      .add("orderKey=" + orderKey)
499      .add("familyKey=" + familyKey)
500      .add("genusKey=" + genusKey)
501      .add("subgenusKey=" + subgenusKey)
502      .add("speciesKey=" + speciesKey)
503      .toString();
504  }
505
506  public enum MatchType {
507
508    /**
509     * An exact, straight match.
510     */
511    EXACT,
512
513    /**
514     * A fuzzy, non exact match.
515     */
516    FUZZY,
517
518    /**
519     * Matching on a higher rank than the lowest name given.
520     */
521    HIGHERRANK,
522
523    /**
524     * Matching on species/infraspecies level
525     * when the verbatim data in fact referred to a broader species aggregate/complex.
526     * @see <a href="https://github.com/gbif/portal-feedback/issues/2935">gbif/portal-feedback#2935</a>
527     */
528    AGGREGATE,
529
530    /**
531     * No match or matching several names with too little information to keep apart.
532     */
533    NONE
534  }
535}