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