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