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.Constants;
017import org.gbif.api.model.common.LinneanClassification;
018import org.gbif.api.model.common.LinneanClassificationKeys;
019import org.gbif.api.util.ClassificationUtils;
020import org.gbif.api.vocabulary.NameType;
021import org.gbif.api.vocabulary.NameUsageIssue;
022import org.gbif.api.vocabulary.NomenclaturalStatus;
023import org.gbif.api.vocabulary.Origin;
024import org.gbif.api.vocabulary.Rank;
025import org.gbif.api.vocabulary.TaxonomicStatus;
026
027import java.net.URI;
028import java.util.Date;
029import java.util.EnumSet;
030import java.util.HashSet;
031import java.util.LinkedHashMap;
032import java.util.Objects;
033import java.util.Set;
034import java.util.StringJoiner;
035import java.util.UUID;
036
037import javax.annotation.Nullable;
038import javax.validation.constraints.NotNull;
039
040import com.fasterxml.jackson.annotation.JsonIgnore;
041import com.fasterxml.jackson.annotation.JsonProperty;
042
043import io.swagger.v3.oas.annotations.media.Schema;
044
045/**
046 * A usage of a <em>scientific name</em> according to one particular Checklist including the GBIF Taxonomic Backbone,
047 * the NUB. It is shown as species in the portal and API.
048 * <br>
049 * Backbone (NUB) usages have key==nubKey. Backbone usages can also be detected by either the NameUsage.isNub() method
050 * or by manually comparing the datasetKey with the fixed backbone datasetKey, see Constants.NUB_DATASET_KEY.
051 * <br>
052 * Name usages from other checklists with names that also exist in the backbone will have a nubKey that points to the related usage in the NUB.
053 * <br>
054 * To store not eagerly loaded subresources such as vernacular names or synonyms with a usage please use
055 * the {@link NameUsageContainer} class.
056 */
057@SuppressWarnings("unused")
058public class NameUsage implements LinneanClassification, LinneanClassificationKeys {
059
060  private Integer key;
061  private Integer nubKey;
062  private Integer nameKey;
063  private String taxonID;
064  private Integer sourceTaxonKey;
065  // for LinneanClassification
066  private String kingdom;
067  private String phylum;
068  @JsonProperty("class")
069  private String clazz;
070  private String order;
071  private String family;
072  private String genus;
073  private String subgenus;
074  private String species;
075  // for LinneanClassificationKeys
076  private Integer kingdomKey;
077  private Integer phylumKey;
078  private Integer classKey;
079  private Integer orderKey;
080  private Integer familyKey;
081  private Integer genusKey;
082  private Integer subgenusKey;
083  private Integer speciesKey;
084
085  private UUID datasetKey;
086  private UUID constituentKey;
087  private Integer parentKey;
088  private String parent;
089  private Integer proParteKey;
090  private Integer acceptedKey;
091  private String accepted;
092  private Integer basionymKey;
093  private String basionym;
094
095  private String scientificName;
096  private String canonicalName;
097  private String vernacularName;
098  private String authorship;
099  private NameType nameType;
100  private Rank rank;
101  private Origin origin;
102  private TaxonomicStatus taxonomicStatus;
103  private Set<NomenclaturalStatus> nomenclaturalStatus = new HashSet<>();
104  private String remarks;
105  private String publishedIn;
106  private String accordingTo;
107
108  private int numDescendants;
109  private URI references;
110
111  private Date modified;
112  private Date deleted;
113  private Date lastCrawled;
114  private Date lastInterpreted;
115  private Set<NameUsageIssue> issues = EnumSet.noneOf(NameUsageIssue.class);
116
117  public NameUsage() {}
118
119  public NameUsage(NameUsage other) {
120    this.key = other.key;
121    this.nubKey = other.nubKey;
122    this.nameKey = other.nameKey;
123    this.taxonID = other.taxonID;
124    this.sourceTaxonKey = other.sourceTaxonKey;
125    this.kingdom = other.kingdom;
126    this.phylum = other.phylum;
127    this.clazz = other.clazz;
128    this.order = other.order;
129    this.family = other.family;
130    this.genus = other.genus;
131    this.subgenus = other.subgenus;
132    this.species = other.species;
133    this.kingdomKey = other.kingdomKey;
134    this.phylumKey = other.phylumKey;
135    this.classKey = other.classKey;
136    this.orderKey = other.orderKey;
137    this.familyKey = other.familyKey;
138    this.genusKey = other.genusKey;
139    this.subgenusKey = other.subgenusKey;
140    this.speciesKey = other.speciesKey;
141    this.datasetKey = other.datasetKey;
142    this.constituentKey = other.constituentKey;
143    this.parentKey = other.parentKey;
144    this.parent = other.parent;
145    this.proParteKey = other.proParteKey;
146    this.acceptedKey = other.acceptedKey;
147    this.accepted = other.accepted;
148    this.basionymKey = other.basionymKey;
149    this.basionym = other.basionym;
150    this.scientificName = other.scientificName;
151    this.canonicalName = other.canonicalName;
152    this.vernacularName = other.vernacularName;
153    this.authorship = other.authorship;
154    this.nameType = other.nameType;
155    this.rank = other.rank;
156    this.origin = other.origin;
157    this.taxonomicStatus = other.taxonomicStatus;
158    this.nomenclaturalStatus = other.nomenclaturalStatus;
159    this.remarks = other.remarks;
160    this.publishedIn = other.publishedIn;
161    this.accordingTo = other.accordingTo;
162    this.numDescendants = other.numDescendants;
163    this.references = other.references;
164    this.modified = other.modified;
165    this.deleted = other.deleted;
166    this.lastCrawled = other.lastCrawled;
167    this.lastInterpreted = other.lastInterpreted;
168    this.issues = other.issues;
169  }
170
171  /**
172   * @return the name key for retrieving a parsed name object
173   */
174  @Schema(description = "The key for retrieving a parsed name object.\n\n" +
175    "*You are more likely to need the `key` or `nubKey` properties*")
176  public Integer getNameKey() {
177    return nameKey;
178  }
179
180  public void setNameKey(Integer nameKey) {
181    this.nameKey = nameKey;
182  }
183
184  /**
185   * For backbone taxa the source taxon key refers to the original name usage that was used during backbone building
186   * and is the primary reason that this taxon exists in the backbone.
187   * <br/>
188   * All backbone name usages are built from several underlying checklist usages,
189   * but these are sorted by priority and the usage key for the highest priority one becomes the sourceTaxonKey
190   * for a backbone usage.
191   * <br/>
192   * Some backbone usages do not have any source record altogether.
193   * For example if there is a subspecies found, but no matching parent species,
194   * the missing species will be created nevertheless and has no primary source.
195   *
196   * @return The key of the name usage this backbone taxon is derived from.
197   */
198  @Nullable
199  @Schema(description = "The key of the name usage from which this backbone taxon derives.\n" +
200    "\n" +
201    "For backbone taxa the source taxon key refers to the original name usage that was used during " +
202    "backbone building and is the primary reason that this taxon exists in the backbone.\n" +
203    "\n" +
204    "All backbone name usages are built from several underlying checklist usages, but these are sorted by priority " +
205    "and the usage key for the highest priority one becomes the sourceTaxonKey for a backbone usage.\n" +
206    "\n" +
207    "Some backbone usages do not have any source record at all; for example if there is a subspecies found, but no matching " +
208    "parent species, the missing species will be created nevertheless and has no primary source.")
209  public Integer getSourceTaxonKey() {
210    return sourceTaxonKey;
211  }
212
213  public void setSourceTaxonKey(Integer sourceTaxonKey) {
214    this.sourceTaxonKey = sourceTaxonKey;
215  }
216
217  /**
218   * @return the scientific name of the accepted name
219   */
220  @Schema(description = "The scientific name of the accepted name.")
221  public String getAccepted() {
222    return accepted;
223  }
224
225  /**
226   * Sets the scientific name of the basionym, i.e. original name usage.
227   */
228  public void setAccepted(String accepted) {
229    this.accepted = accepted;
230  }
231
232  /**
233   * @return the name usage key of the accepted name
234   */
235  @Schema(description = "The name usage key of the accepted name.")
236  public Integer getAcceptedKey() {
237    return acceptedKey;
238  }
239
240  /**
241   * Sets the usage key for the accepted name.
242   */
243  public void setAcceptedKey(Integer acceptedKey) {
244    this.acceptedKey = acceptedKey;
245  }
246
247  /**
248   * The taxon concept reference is usually a reference to some publication or author and year.
249   * <br>
250   * The dwc:taxonAccordingTo reference is usually appended to the scientific name to further qualify the concept
251   * with "sensu" or "sec." being used for concatenation. E.g. "Acer nigrum sec. Gleason Cronquist 1991".
252   * <br>
253   * In the case of backbone taxa this refers to the primary checklist the name was found in.
254   *
255   * @return the taxon concept reference
256   */
257  @Schema(description = "The taxon concept reference.\n\n" +
258    "This is usually a reference to some publication or an author and year.\n\n" +
259    "The Darwin Core `taxonAccordingTo` reference is usually appended to the scientific name to further qualify " +
260    "the concept with “sensu” or “sec.” being used for concatenation; for example “_Acer nigrum_ sec. Gleason Cronquist 1991”.\n\n" +
261    "In the case of backbone taxa, this refers to the primary checklist in which the name was found.")
262  @Nullable
263  public String getAccordingTo() {
264    return accordingTo;
265  }
266
267  /**
268   * @param accordingTo the accordingTo to set
269   */
270  public void setAccordingTo(String accordingTo) {
271    this.accordingTo = accordingTo;
272  }
273
274  /**
275   * Returns the authorship information for the scientific name.
276   *
277   * @return the authorship
278   */
279  @Schema(description = "The authorship for the scientific name.")
280  @Nullable
281  public String getAuthorship() {
282    return authorship;
283  }
284
285  /**
286   * @param authorship the authorship to set
287   */
288  public void setAuthorship(String authorship) {
289    this.authorship = authorship;
290  }
291
292  /**
293   * @return the scientific name of the basionym
294   */
295  @Schema(description = "The scientific name of the basionym.")
296  public String getBasionym() {
297    return basionym;
298  }
299
300  /**
301   * sets the basionym name.
302   */
303  public void setBasionym(String basionym) {
304    this.basionym = basionym;
305  }
306
307  /**
308   * Returns the earlier name (basionym) for this scientific name. Return null if the basionym does not exist.
309   *
310   * @return the basionymKey
311   */
312  @Schema(description = "The name usage key of the basionym.")
313  @Nullable
314  public Integer getBasionymKey() {
315    return basionymKey;
316  }
317
318  /**
319   * @param basionymKey the basionymKey to set
320   */
321  public void setBasionymKey(Integer basionymKey) {
322    this.basionymKey = basionymKey;
323  }
324
325  /**
326   * @return the canonicalName
327   */
328  @Schema(description = "The canonical name; the name without authorship or references.")
329  @Nullable
330  public String getCanonicalName() {
331    return canonicalName;
332  }
333
334  /**
335   * @param canonicalName the canonicalName to set
336   */
337  public void setCanonicalName(String canonicalName) {
338    this.canonicalName = canonicalName;
339  }
340
341  /**
342   * Returns the key of the checklist that "hosts" this name usage.
343   *
344   * @return the datasetKey
345   */
346  @Schema(description = "The checklist that “hosts” this name usage.\n\n" +
347    "For a backbone name usage, this will be `d7dddbf4-2cf0-4f39-9b2a-bb099caae36c`.")
348  @NotNull
349  public UUID getDatasetKey() {
350    return datasetKey;
351  }
352
353  /**
354   * @param datasetKey the datasetKey to set
355   */
356  public void setDatasetKey(UUID datasetKey) {
357    this.datasetKey = datasetKey;
358  }
359
360  /**
361   * Return the key that uniquely identifies this name usage.
362   *
363   * @return the key
364   */
365  @Schema(description = "The name usage key that uniquely identifies this name usage.")
366  @NotNull
367  public Integer getKey() {
368    return key;
369  }
370
371  /**
372   * @param key the key to set
373   */
374  public void setKey(Integer key) {
375    this.key = key;
376  }
377
378  /**
379   * @return the type of name string classified by CLB.
380   */
381  @Schema(description = "The type of name string classified by Checklistbank.")
382  public NameType getNameType() {
383    return nameType;
384  }
385
386  /**
387   * @param nameType the type of name string
388   */
389  public void setNameType(NameType nameType) {
390    this.nameType = nameType;
391  }
392
393  /**
394   * The status related to the conformance to the relevant rules of nomenclature.
395   * <blockquote>
396   * <p>
397   * <i>Example:</i> "invalid", "misapplied", "homotypic synonym", "accepted"
398   * </p>
399   * </blockquote>
400   *
401   * @return the set of known nomenclaturalStatus values
402   *
403   * @see <a href="http://rs.gbif.org/vocabulary/gbif/nomenclatural_status.xml">Nomenclatural Status GBIF
404   *      Vocabulary</a>
405   */
406  @Schema(description = "The nomenclatural statuses of this name usage.")
407  public Set<NomenclaturalStatus> getNomenclaturalStatus() {
408    return nomenclaturalStatus;
409  }
410
411  /**
412   * @param nomenclaturalStatus the nomenclaturalStatus to set
413   */
414  public void setNomenclaturalStatus(Set<NomenclaturalStatus> nomenclaturalStatus) {
415    this.nomenclaturalStatus = nomenclaturalStatus;
416  }
417
418  /**
419   * @return the taxon key of the matching backbone name usage
420   */
421  @Schema(description = "The taxon key of the matching backbone name usage.\n\n" +
422    "If this is equal to `key`, this name usage is a backbone name usage.")
423  @Nullable
424  public Integer getNubKey() {
425    return nubKey;
426  }
427
428  /**
429   * @param nubKey the nubKey to set
430   */
431  public void setNubKey(Integer nubKey) {
432    this.nubKey = nubKey;
433  }
434
435  /**
436   * The number of all accepted taxonomic elements under this usage.
437   *
438   * @return the number of descendants
439   */
440  @Schema(description = "A total count of all accepted taxonomic elements under this usage.")
441  public int getNumDescendants() {
442    return numDescendants;
443  }
444
445  /**
446   * @param numDescendants the n umber of descendants to set
447   */
448  public void setNumDescendants(int numDescendants) {
449    this.numDescendants = numDescendants;
450  }
451
452  /**
453   * The origin of this name usage record, i.e. the reason why it exists.
454   * In most cases this is because the record existed explicitly in the checklist sources, but
455   * some usages are created de novo because they exist implicitly in the data.
456   *
457   * @return the name usage origin
458   *
459   * @see Origin
460   */
461  @Schema(description = "The name usage origin.\n\n" +
462    "The origin of this name usage record, the reason it exists.\n\n" +
463    "In most cases this is because the record existed explicitly in the checklist sources, but some usages are " +
464    "created *de novo* because the exist implicitly in the data.")
465  @NotNull
466  public Origin getOrigin() {
467    return origin;
468  }
469
470  /**
471   * @param origin the origin to set
472   */
473  public void setOrigin(Origin origin) {
474    this.origin = origin;
475  }
476
477  /**
478   * The scientific name of the parent.
479   *
480   * @return the parent name
481   */
482  @Schema(description = "The scientific name of the parent.")
483  public String getParent() {
484    return parent;
485  }
486
487  /**
488   * @param parent the parent name to set
489   */
490  public void setParent(String parent) {
491    this.parent = parent;
492  }
493
494  /**
495   * Returns the immediate parent. If this usage if for the highest taxonomic level, return null.
496   *
497   * @return the parentKey
498   */
499  @Schema(description = "The name usage key of the immediate parent.  Null for the highest taxonomic level.")
500  @Nullable
501  public Integer getParentKey() {
502    return parentKey;
503  }
504
505  /**
506   * @param parentKey the parentKey to set
507   */
508  public void setParentKey(Integer parentKey) {
509    this.parentKey = parentKey;
510  }
511
512  /**
513   * Pro parte synonyms, i.e. a synonym with multiple accepted names, are grouped by a single, primary name usage key.
514   *
515   * @return the primary name usage key for a pro parte synonym or null
516   */
517  @Schema(description = "The primary name usage key for a *pro parte* synonym.\n\n" +
518    "Synonyms with multiple accepted names are grouped by a single, primary name usage key.")
519  public Integer getProParteKey() {
520    return proParteKey;
521  }
522
523  /**
524   * Sets the pro parte usage key.
525   */
526  public void setProParteKey(Integer proParteKey) {
527    this.proParteKey = proParteKey;
528  }
529
530  /**
531   * Original publication for this name usage.
532   *
533   * @return the publishedIn
534   */
535  @Schema(description = "Original publication for this name usage.")
536  @Nullable
537  public String getPublishedIn() {
538    return publishedIn;
539  }
540
541  /**
542   * @param publishedIn the publishedIn to set
543   */
544  public void setPublishedIn(String publishedIn) {
545    this.publishedIn = publishedIn;
546  }
547
548  /**
549   * Returns the rank for this usage.
550   * <blockquote>
551   * <p>
552   * <i>Example:</i> "Kingdom", "Genus"
553   * </p>
554   * </blockquote>
555   *
556   * @return the rank
557   */
558  @Schema(description = "The rank for this usage.")
559  @Nullable
560  public Rank getRank() {
561    return rank;
562  }
563
564  /**
565   * @param rank the rank to set
566   */
567  public void setRank(Rank rank) {
568    this.rank = rank;
569  }
570
571  /**
572   * The scientific name (with date and authorship information if applicable).
573   * <blockquote>
574   * <p>
575   * <i>Example:</i> "Coleoptera" (order), "Vespertilionidae" (family), "Manis" (genus), "Ctenomys sociabilis" (genus +
576   * specific name), "Ambystoma tigrinum diaboli" (genus + specific name + infraspecific name),
577   * "Quercus agrifolia var. oxyadenia (Torr.)"
578   * </p>
579   * </blockquote>
580   *
581   * @return the scientificName
582   */
583  @Schema(description = "The scientific name, with date and authorship information if available.\n\n" +
584    "Examples: *Coleoptera* (order), *Vespertilionidae* (family), *Manis* (genus), *Ctenomys sociabilis* " +
585    "(genus + specificEpithet), *Ambystoma tigrinum diaboli* (zoology, genus + specific name + infraspecific name) " +
586    "*Quercus agrifolia* var. *oxyadenia* (Torr.) (botany, genus + specific epithet + infraspecific epithet + authorship)")
587  @NotNull
588  public String getScientificName() {
589    return scientificName;
590  }
591
592  /**
593   * @param scientificName the scientificName to set
594   */
595  public void setScientificName(String scientificName) {
596    this.scientificName = scientificName;
597  }
598
599  /**
600   * Return the optional sub dataset key for this usage.
601   *
602   * @return the subDatasetKey or null
603   */
604  @Schema(description = "The optional sub-dataset key for this usage.")
605  @Nullable
606  public UUID getConstituentKey() {
607    return constituentKey;
608  }
609
610  /**
611   * @param constituentKey to set
612   */
613  public void setConstituentKey(UUID constituentKey) {
614    this.constituentKey = constituentKey;
615  }
616
617  /**
618   * A common or vernacular name for this usage.
619   * <blockquote>
620   * <p>
621   * <i>Example:</i> Andean Condor", "Condor Andino", "American Eagle", "Gänsegeier".
622   * </p>
623   * </blockquote>
624   *
625   * @return the vernacularName
626   */
627  @Schema(description = "A common or vernacular name for this usage.")
628  @Nullable
629  public String getVernacularName() {
630    return vernacularName;
631  }
632
633  /**
634   * @param vernacularName the vernacularName to set
635   */
636  public void setVernacularName(String vernacularName) {
637    this.vernacularName = vernacularName;
638  }
639
640  @Schema(description = "Kingdom.")
641  @Override
642  public String getKingdom() {
643    return kingdom;
644  }
645
646  @Override
647  public void setKingdom(String kingdom) {
648    this.kingdom = kingdom;
649  }
650
651  @Schema(description = "Phylum.")
652  @Override
653  public String getPhylum() {
654    return phylum;
655  }
656
657  @Override
658  public void setPhylum(String phylum) {
659    this.phylum = phylum;
660  }
661
662  @Schema(description = "Class.")
663  @Override
664  public String getClazz() {
665    return clazz;
666  }
667
668  @Override
669  public void setClazz(String clazz) {
670    this.clazz = clazz;
671  }
672
673  @Schema(description = "Order.")
674  @Override
675  public String getOrder() {
676    return order;
677  }
678
679  @Override
680  public void setOrder(String order) {
681    this.order = order;
682  }
683
684  @Schema(description = "Family.")
685  @Override
686  public String getFamily() {
687    return family;
688  }
689
690  @Override
691  public void setFamily(String family) {
692    this.family = family;
693  }
694
695  @Schema(description = "Genus.")
696  @Override
697  public String getGenus() {
698    return genus;
699  }
700
701  @Override
702  public void setGenus(String genus) {
703    this.genus = genus;
704  }
705
706  @Schema(description = "Subgenus.")
707  @Override
708  public String getSubgenus() {
709    return subgenus;
710  }
711
712  @Override
713  public void setSubgenus(String subgenus) {
714    this.subgenus = subgenus;
715  }
716
717  @Schema(description = "Species.")
718  @Override
719  public String getSpecies() {
720    return species;
721  }
722
723  @Override
724  public void setSpecies(String species) {
725    this.species = species;
726  }
727
728  @Schema(description = "Name usage key of the kingdom.")
729  @Override
730  public Integer getKingdomKey() {
731    return kingdomKey;
732  }
733
734  @Override
735  public void setKingdomKey(Integer kingdomKey) {
736    this.kingdomKey = kingdomKey;
737  }
738
739  @Schema(description = "Name usage key of the phylum.")
740  @Override
741  public Integer getPhylumKey() {
742    return phylumKey;
743  }
744
745  @Override
746  public void setPhylumKey(Integer phylumKey) {
747    this.phylumKey = phylumKey;
748  }
749
750  @Schema(description = "Name usage key of the class.")
751  @Override
752  public Integer getClassKey() {
753    return classKey;
754  }
755
756  @Override
757  public void setClassKey(Integer classKey) {
758    this.classKey = classKey;
759  }
760
761  @Schema(description = "Name usage key of the order.")
762  @Override
763  public Integer getOrderKey() {
764    return orderKey;
765  }
766
767  @Override
768  public void setOrderKey(Integer orderKey) {
769    this.orderKey = orderKey;
770  }
771
772  @Schema(description = "Name usage key of the family.")
773  @Override
774  public Integer getFamilyKey() {
775    return familyKey;
776  }
777
778  @Override
779  public void setFamilyKey(Integer familyKey) {
780    this.familyKey = familyKey;
781  }
782
783  @Schema(description = "Name usage key of the genus.")
784  @Override
785  public Integer getGenusKey() {
786    return genusKey;
787  }
788
789  @Override
790  public void setGenusKey(Integer genusKey) {
791    this.genusKey = genusKey;
792  }
793
794  @Schema(description = "Name usage key of the subgenus.")
795  @Override
796  public Integer getSubgenusKey() {
797    return subgenusKey;
798  }
799
800  @Override
801  public void setSubgenusKey(Integer subgenusKey) {
802    this.subgenusKey = subgenusKey;
803  }
804
805  @Schema(description = "Name usage key of the species.")
806  @Override
807  public Integer getSpeciesKey() {
808    return speciesKey;
809  }
810
811  @Override
812  public void setSpeciesKey(Integer speciesKey) {
813    this.speciesKey = speciesKey;
814  }
815
816  @Schema(description = "Remarks on the name usage.")
817  public String getRemarks() {
818    return remarks;
819  }
820
821  public void setRemarks(String remarks) {
822    this.remarks = remarks;
823  }
824
825  /**
826   * @return the canonicalName or scientific name in case its null
827   */
828  @Nullable
829  @JsonIgnore
830  public String getCanonicalOrScientificName() {
831    return canonicalName == null ? scientificName : canonicalName;
832  }
833
834  /**
835   * A URI link or reference to the source of this record, the record's "homepage".
836   * <blockquote>
837   * <p>
838   * <i>Example:</i> https://www.catalogueoflife.org/data/taxon/4R5YN
839   * </p>
840   * </blockquote>
841   *
842   * @return the link
843   */
844  @Schema(description = "A URI link or reference to the source of the record, the record's “homepage”.")
845  @Nullable
846  public URI getReferences() {
847    return references;
848  }
849
850  public void setReferences(URI references) {
851    this.references = references;
852  }
853
854  @Override
855  public String getHigherRank(Rank rank) {
856    return ClassificationUtils.getHigherRank(this, rank);
857  }
858
859  @Override
860  public Integer getHigherRankKey(Rank rank) {
861    return ClassificationUtils.getHigherRankKey(this, rank);
862  }
863
864  /**
865   * An ordered map with entries for all higher Linnean ranks down to the actual direct parent of this usage.
866   * The map starts with the highest rank, e.g. the kingdom and maps the name usage key to its canonical name.
867   * The name usage itself is never included, even though a higher rank might point to the usage itself.
868   *
869   * @return map of higher ranks
870   */
871  @NotNull
872  @JsonIgnore
873  public LinkedHashMap<Integer, String> getHigherClassificationMap() {
874    return ClassificationUtils.getHigherClassificationMap(this, key, parentKey, parent);
875  }
876
877  /**
878   * The original taxonID of the name usage as found in the source.
879   * For backbone taxa and name usages with an origin different to SOURCE this is null.
880   */
881  @Schema(description = "The original taxonID of the name usage as found in the source.\n\n" +
882    "For backbone taxa and name usages with an origin different to SOURCE this is null.")
883  @Nullable
884  public String getTaxonID() {
885    return taxonID;
886  }
887
888  /**
889   * The taxonomic status of the name usage.
890   * Can be null, but for all synonyms with an accepted name usage it is guaranteed to exist.
891   *
892   * @return the taxonomicStatus, can be null
893   */
894  @Schema(description = "The taxonomic status of the name usage.\n\n" +
895    "Can be null, but for all synonyms with an accepted name usage it is guaranteed to exist.")
896  @Nullable
897  public TaxonomicStatus getTaxonomicStatus() {
898    return taxonomicStatus;
899  }
900
901  /**
902   * @param taxonomicStatus the taxonomicStatus to set
903   */
904  public void setTaxonomicStatus(TaxonomicStatus taxonomicStatus) {
905    this.taxonomicStatus = taxonomicStatus;
906  }
907
908  /**
909   * The interpreted dc:modified from the verbatim source data.
910   * Ideally indicating when a record was last modified in the source.
911   */
912  @Schema(description = "The interpreted dc:modified from the verbatim source data, ideally indicating when a record " +
913    "was last modified in the source.")
914  @Nullable
915  public Date getModified() {
916    return modified;
917  }
918
919  public void setModified(Date modified) {
920    this.modified = modified;
921  }
922
923  /**
924   * The date this record was deleted.
925   * Logical deletions only occur for backbone usages!
926   */
927  @Schema(description = "The date this record was deleted.\n\n" +
928    "*Only backbone name usages are soft-deleted.*")
929  @Nullable
930  public Date getDeleted() {
931    return deleted;
932  }
933
934  public void setDeleted(Date deleted) {
935    this.deleted = deleted;
936  }
937
938  /**
939   * The date this record was last crawled during clb indexing.
940   */
941  @Schema(description = "The date this record was last crawled (downloaded from the source) during Checklistbank indexing.")
942  @Nullable
943  public Date getLastCrawled() {
944    return lastCrawled;
945  }
946
947  public void setLastCrawled(Date lastCrawled) {
948    this.lastCrawled = lastCrawled;
949  }
950
951  /**
952   * The date this record was last interpreted during indexing.
953   * This includes matching to the backbone.
954   */
955  @Schema(description = "The date this record was last interpreted during indexing.  This includes matching to the backbone.")
956  @Nullable
957  public Date getLastInterpreted() {
958    return lastInterpreted;
959  }
960
961  public void setLastInterpreted(Date lastInterpreted) {
962    this.lastInterpreted = lastInterpreted;
963  }
964
965  @Schema(description = "Data quality issues found during Checklistbank interpretation.")
966  @NotNull
967  public Set<NameUsageIssue> getIssues() {
968    return issues;
969  }
970
971  public void setIssues(Set<NameUsageIssue> issues) {
972    Objects.requireNonNull(issues, "Issues cannot be null");
973    this.issues = issues.isEmpty() ? EnumSet.noneOf(NameUsageIssue.class) : EnumSet.copyOf(issues);
974  }
975
976  public void addIssue(NameUsageIssue issue) {
977    Objects.requireNonNull(issue, "Issue needs to be specified");
978    this.issues.add(issue);
979  }
980
981  @JsonIgnore
982  public boolean isNub() {
983    return datasetKey.equals(Constants.NUB_DATASET_KEY);
984  }
985
986  /**
987   * True for pro parte synonyms with multiple accepted usages.
988   *
989   * @return true if proParte, false otherwise
990   */
991  @JsonIgnore
992  public boolean isProParte() {
993    return proParteKey != null;
994  }
995
996  /**
997   * Convenience method using the taxonomicStatus field.
998   * @return true if it's a synonym
999   */
1000  @JsonIgnore
1001  public boolean isSynonym() {
1002    return taxonomicStatus != null && taxonomicStatus.isSynonym();
1003  }
1004
1005  public void setTaxonID(String taxonID) {
1006    this.taxonID = taxonID;
1007  }
1008
1009  @Override
1010  public boolean equals(Object o) {
1011    if (this == o) {
1012      return true;
1013    }
1014    if (o == null || getClass() != o.getClass()) {
1015      return false;
1016    }
1017    NameUsage nameUsage = (NameUsage) o;
1018    return numDescendants == nameUsage.numDescendants &&
1019      Objects.equals(key, nameUsage.key) &&
1020      Objects.equals(nubKey, nameUsage.nubKey) &&
1021      Objects.equals(nameKey, nameUsage.nameKey) &&
1022      Objects.equals(taxonID, nameUsage.taxonID) &&
1023      Objects.equals(kingdom, nameUsage.kingdom) &&
1024      Objects.equals(phylum, nameUsage.phylum) &&
1025      Objects.equals(clazz, nameUsage.clazz) &&
1026      Objects.equals(order, nameUsage.order) &&
1027      Objects.equals(family, nameUsage.family) &&
1028      Objects.equals(genus, nameUsage.genus) &&
1029      Objects.equals(subgenus, nameUsage.subgenus) &&
1030      Objects.equals(species, nameUsage.species) &&
1031      Objects.equals(kingdomKey, nameUsage.kingdomKey) &&
1032      Objects.equals(phylumKey, nameUsage.phylumKey) &&
1033      Objects.equals(classKey, nameUsage.classKey) &&
1034      Objects.equals(orderKey, nameUsage.orderKey) &&
1035      Objects.equals(familyKey, nameUsage.familyKey) &&
1036      Objects.equals(genusKey, nameUsage.genusKey) &&
1037      Objects.equals(subgenusKey, nameUsage.subgenusKey) &&
1038      Objects.equals(speciesKey, nameUsage.speciesKey) &&
1039      Objects.equals(datasetKey, nameUsage.datasetKey) &&
1040      Objects.equals(constituentKey, nameUsage.constituentKey) &&
1041      Objects.equals(parentKey, nameUsage.parentKey) &&
1042      Objects.equals(parent, nameUsage.parent) &&
1043      Objects.equals(proParteKey, nameUsage.proParteKey) &&
1044      Objects.equals(acceptedKey, nameUsage.acceptedKey) &&
1045      Objects.equals(accepted, nameUsage.accepted) &&
1046      Objects.equals(basionymKey, nameUsage.basionymKey) &&
1047      Objects.equals(basionym, nameUsage.basionym) &&
1048      Objects.equals(scientificName, nameUsage.scientificName) &&
1049      Objects.equals(canonicalName, nameUsage.canonicalName) &&
1050      Objects.equals(vernacularName, nameUsage.vernacularName) &&
1051      Objects.equals(authorship, nameUsage.authorship) &&
1052      nameType == nameUsage.nameType &&
1053      rank == nameUsage.rank &&
1054      origin == nameUsage.origin &&
1055      taxonomicStatus == nameUsage.taxonomicStatus &&
1056      Objects.equals(nomenclaturalStatus, nameUsage.nomenclaturalStatus) &&
1057      Objects.equals(remarks, nameUsage.remarks) &&
1058      Objects.equals(publishedIn, nameUsage.publishedIn) &&
1059      Objects.equals(accordingTo, nameUsage.accordingTo) &&
1060      Objects.equals(references, nameUsage.references) &&
1061      Objects.equals(modified, nameUsage.modified) &&
1062      Objects.equals(deleted, nameUsage.deleted) &&
1063      Objects.equals(lastCrawled, nameUsage.lastCrawled) &&
1064      Objects.equals(lastInterpreted, nameUsage.lastInterpreted) &&
1065      Objects.equals(issues, nameUsage.issues);
1066  }
1067
1068  @Override
1069  public int hashCode() {
1070    return Objects
1071      .hash(key, nubKey, nameKey, taxonID, kingdom, phylum, clazz, order, family, genus, subgenus,
1072        species, kingdomKey, phylumKey, classKey, orderKey, familyKey, genusKey, subgenusKey,
1073        speciesKey, datasetKey, constituentKey, parentKey, parent, proParteKey, acceptedKey,
1074        accepted, basionymKey, basionym, scientificName, canonicalName, vernacularName,
1075        authorship, nameType, rank, origin, taxonomicStatus, nomenclaturalStatus, remarks,
1076        publishedIn, accordingTo, numDescendants, references, modified, deleted, lastCrawled,
1077        lastInterpreted, issues);
1078  }
1079
1080  @Override
1081  public String toString() {
1082    return new StringJoiner(", ", NameUsage.class.getSimpleName() + "[", "]")
1083      .add("key=" + key)
1084      .add("nubKey=" + nubKey)
1085      .add("nameKey=" + nameKey)
1086      .add("taxonID='" + taxonID + "'")
1087      .add("sourceTaxonKey=" + sourceTaxonKey)
1088      .add("kingdom='" + kingdom + "'")
1089      .add("phylum='" + phylum + "'")
1090      .add("clazz='" + clazz + "'")
1091      .add("order='" + order + "'")
1092      .add("family='" + family + "'")
1093      .add("genus='" + genus + "'")
1094      .add("subgenus='" + subgenus + "'")
1095      .add("species='" + species + "'")
1096      .add("kingdomKey=" + kingdomKey)
1097      .add("phylumKey=" + phylumKey)
1098      .add("classKey=" + classKey)
1099      .add("orderKey=" + orderKey)
1100      .add("familyKey=" + familyKey)
1101      .add("genusKey=" + genusKey)
1102      .add("subgenusKey=" + subgenusKey)
1103      .add("speciesKey=" + speciesKey)
1104      .add("datasetKey=" + datasetKey)
1105      .add("constituentKey=" + constituentKey)
1106      .add("parentKey=" + parentKey)
1107      .add("parent='" + parent + "'")
1108      .add("proParteKey=" + proParteKey)
1109      .add("acceptedKey=" + acceptedKey)
1110      .add("accepted='" + accepted + "'")
1111      .add("basionymKey=" + basionymKey)
1112      .add("basionym='" + basionym + "'")
1113      .add("scientificName='" + scientificName + "'")
1114      .add("canonicalName='" + canonicalName + "'")
1115      .add("vernacularName='" + vernacularName + "'")
1116      .add("authorship='" + authorship + "'")
1117      .add("nameType=" + nameType)
1118      .add("rank=" + rank)
1119      .add("origin=" + origin)
1120      .add("taxonomicStatus=" + taxonomicStatus)
1121      .add("nomenclaturalStatus=" + nomenclaturalStatus)
1122      .add("remarks='" + remarks + "'")
1123      .add("publishedIn='" + publishedIn + "'")
1124      .add("accordingTo='" + accordingTo + "'")
1125      .add("numDescendants=" + numDescendants)
1126      .add("references=" + references)
1127      .add("modified=" + modified)
1128      .add("deleted=" + deleted)
1129      .add("lastCrawled=" + lastCrawled)
1130      .add("lastInterpreted=" + lastInterpreted)
1131      .add("issues=" + issues)
1132      .toString();
1133  }
1134}