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.vocabulary;
015
016import java.util.Arrays;
017import java.util.Collections;
018import java.util.HashMap;
019import java.util.HashSet;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023
024import io.swagger.v3.oas.annotations.ExternalDocumentation;
025import io.swagger.v3.oas.annotations.media.Schema;
026
027/**
028 * An ordered taxonomic rank enumeration with the most frequently used values.
029 * Several static methods, lists, sets and maps are provided to help with ordering and lookup from strings.
030 *
031 * @see <a href="http://rs.gbif.org/vocabulary/gbif/rank.xml">rs.gbif.org vocabulary</a>
032 */
033@Schema(
034  externalDocs = @ExternalDocumentation(
035    description = "Darwin Core definition",
036    url = "https://rs.tdwg.org/dwc/terms/taxonRank"
037  )
038)
039@SuppressWarnings("unused")
040public enum Rank {
041
042  DOMAIN("dom."),
043
044  SUPERKINGDOM("superreg."),
045
046  KINGDOM("reg."),
047
048  SUBKINGDOM("subreg."),
049
050  INFRAKINGDOM("infrareg."),
051
052  SUPERPHYLUM("superphyl."),
053
054  PHYLUM("phyl."),
055
056  SUBPHYLUM("subphyl."),
057
058  INFRAPHYLUM("infraphyl."),
059
060  SUPERCLASS("supercl."),
061
062  CLASS("cl."),
063
064  SUBCLASS("subcl."),
065
066  INFRACLASS("infracl."),
067
068  PARVCLASS("parvcl."),
069
070  SUPERLEGION("superleg."),
071
072  /**
073   * Sometimes used in zoology, e.g. for birds and mammals
074   */
075  LEGION("leg."),
076
077  SUBLEGION("subleg."),
078
079  INFRALEGION("infraleg."),
080
081  SUPERCOHORT("supercohort"),
082
083  /**
084   * Sometimes used in zoology, e.g. for birds and mammals
085   */
086  COHORT("cohort"),
087
088  SUBCOHORT("subcohort"),
089
090  INFRACOHORT("infracohort"),
091
092  MAGNORDER("magnord."),
093
094  SUPERORDER("superord."),
095
096  GRANDORDER("grandord."),
097
098  ORDER("ord."),
099
100  SUBORDER("subord."),
101
102  INFRAORDER("infraord."),
103
104  PARVORDER("parvord."),
105
106  SUPERFAMILY("superfam."),
107
108  FAMILY("fam."),
109
110  SUBFAMILY("subfam."),
111
112  INFRAFAMILY("infrafam."),
113
114  SUPERTRIBE("supertrib."),
115
116  TRIBE("trib."),
117
118  SUBTRIBE("subtrib."),
119
120  INFRATRIBE("infratrib."),
121
122  /**
123   * Used for any other unspecific rank above genera.
124   */
125  SUPRAGENERIC_NAME("supragen."),
126
127  GENUS("gen."),
128
129  SUBGENUS("subgen."),
130
131  INFRAGENUS("infragen."),
132
133  SECTION("sect."),
134
135  SUBSECTION("subsect."),
136
137  SERIES("ser."),
138
139  SUBSERIES("subser."),
140
141  /**
142   * used for any other unspecific rank below genera and above species.
143   */
144  INFRAGENERIC_NAME("infrageneric"),
145
146  /**
147   * A loosely defined group of species.
148   * Zoology: Aggregate - a group of species, other than a subgenus, within a genus. An aggregate may be denoted by a group name interpolated in parentheses.
149   * The Berlin/MoreTax model notes: [these] aren't taxonomic ranks but cirumscriptions because on the one hand they are necessary for the concatenation
150   * of the fullname and on the other hand they are necessary for distinguishing the aggregate or species group from the microspecies.
151   */
152  SPECIES_AGGREGATE("agg."),
153
154  SPECIES("sp."),
155
156  /**
157   * used for any other unspecific rank below species.
158   */
159  INFRASPECIFIC_NAME("infrasp."),
160
161  /**
162   * The term grex has been coined to expand botanical nomenclature to describe hybrids of orchids.
163   * Grex names are one of the three categories of plant names governed by the International Code of Nomenclature for Cultivated Plants
164   * Within a grex the Groups category can be used to refer to plants by their shared characteristics (rather than by their parentage),
165   * and individual orchid plants can be selected (and propagated) and named as cultivars
166   * https://en.wikipedia.org/wiki/Grex_(horticulture)
167   */
168  GREX("grex"),
169
170  SUBSPECIES("subsp."),
171
172  /**
173   * Rank in use from the code for cultivated plants.
174   * It does not use a classic rank marker but indicated the Group rank after the actual groups name
175   * For example Rhododendron boothii Mishmiense Group
176   * or Primula Border Auricula Group
177   *
178   * Sometimes authors also used the words "sort", "type", "selections" or "hybrids" instead of Group which is not legal according to the code.
179   */
180  CULTIVAR_GROUP,
181
182  /**
183   * A group of cultivars. These can be roughly comparable to cultivar groups, but convarieties, unlike cultivar groups,
184   * do not necessarily contain named varieties, and convarieties are members of traditional "Linnaean" ranks.
185   * The ICNCP replaced this term with the term cultivar-group, and convarieties should not be used in modern cultivated plant taxonomy.
186   *
187   * From Spooner et al., Horticultural Reviews 28 (2003): 1-60
188   */
189  CONVARIETY("convar."),
190
191  /**
192   * used also for any other unspecific rank below subspecies.
193   */
194  INFRASUBSPECIFIC_NAME("infrasubsp."),
195
196  /**
197   * Botanical legacy rank
198   */
199
200  PROLES("prol."),
201
202  /**
203   * Botanical legacy rank
204   */
205  RACE("race"),
206
207  /**
208   * Zoological legacy rank
209   */
210  NATIO("natio"),
211
212  /**
213   * Zoological legacy rank
214   */
215  ABERRATION("ab."),
216
217  /**
218   * Zoological legacy rank
219   */
220  MORPH("morph"),
221
222  VARIETY("var."),
223
224  SUBVARIETY("subvar."),
225
226  FORM("f."),
227
228  SUBFORM("subf."),
229
230  /**
231   * Microbial rank based on pathogenic reactions in one or more hosts.
232   * For recommendations on designating pathovars and use of designations when reviving names see
233   * Dye et al. (1980) Standards for naming pathovars of phytopathogenic bacteria and a list of pathovar names and pathotype strains.
234   * Rev. Plant Pathol. 59:153–168.
235   * See <a href="http://www.ncbi.nlm.nih.gov/books/NBK8812/table/A844/?report=objectonly">Bacteriological Code</a>
236   * See <a href="http://www.isppweb.org/about_tppb_naming.asp">International Standards for Naming Pathovars of Phytopathogenic Bacteria</a>
237   * See <a href="http://sipav.org/main/jpp/index.php/jpp/article/view/682">Demystifying the nomenclature of bacterial plant pathogens</a>
238   * See <a href="http://link.springer.com/chapter/10.1007/978-94-009-3555-6_171">Problems with the Pathovar Concept</a>
239   * For example Pseudomonas syringae pv. lachrymans
240   */
241  PATHOVAR("pv."),
242
243  /**
244   * Microbial rank based on biochemical or physiological properties.
245   * See <a href="http://www.ncbi.nlm.nih.gov/books/NBK8812/table/A844/?report=objectonly">Bacteriological Code</a>
246   * For example Francisella tularensis biovar tularensis
247   */
248  BIOVAR("biovar"),
249
250  /**
251   * Microbial rank based on production or amount of production of a particular chemical.
252   * See <a href="http://www.ncbi.nlm.nih.gov/books/NBK8812/table/A844/?report=objectonly">Bacteriological Code</a>
253   * For example Vibrio alginolyticus chemovar iophagus
254   */
255  CHEMOVAR("chemovar"),
256
257  /**
258   * Microbial rank based on morphological characterislics.
259   * See <a href="http://www.ncbi.nlm.nih.gov/books/NBK8812/table/A844/?report=objectonly">Bacteriological Code</a>
260   * For example Acinetobacter junii morphovar I
261   */
262  MORPHOVAR("morphovar"),
263
264  /**
265   * Microbial infrasubspecific rank based on reactions to bacteriophage.
266   * See <a href="http://www.ncbi.nlm.nih.gov/books/NBK8812/table/A844/?report=objectonly">Bacteriological Code</a>
267   * For example Staphyloccocus aureus phagovar 42D
268   */
269  PHAGOVAR("phagovar"),
270
271  /**
272   * Microbial infrasubspecific rank based on antigenic characteristics.
273   * See <a href="http://www.ncbi.nlm.nih.gov/books/NBK8812/table/A844/?report=objectonly">Bacteriological Code</a>
274   * For example Salmonella enterica serovar Dublin
275   */
276  SEROVAR("serovar"),
277
278  /**
279   * Microbial infrasubspecific rank based on chemical constitution.
280   * See <a href="http://www.ncbi.nlm.nih.gov/books/NBK8812/table/A844/?report=objectonly">Bacteriological Code</a>
281   * For example Thymus vulgaris ct. geraniol
282   */
283  CHEMOFORM("chemoform"),
284
285  /**
286   * Microbial infrasubspecific rank.
287   * A parasitic, symbiotic, or commensal microorganism distinguished primarily by adaptation to a particular host or habitat.
288   * Named preferably by the scientific name of the host in the genitive.
289   * See <a href="http://www.ncbi.nlm.nih.gov/books/NBK8812/table/A844/?report=objectonly">Bacteriological Code</a>
290   * For example Puccinia graminis f. sp. avenae
291   */
292  FORMA_SPECIALIS("f.sp."),
293
294  CULTIVAR("cv."),
295
296  /**
297   * A microbial strain.
298   */
299  STRAIN("strain"),
300
301  /**
302   * Any other rank we cannot map to this enumeration
303   */
304  OTHER,
305
306  UNRANKED;
307
308  /**
309   * All main Linnean ranks ordered.
310   */
311  public static final List<Rank> LINNEAN_RANKS = Collections.unmodifiableList(
312    Arrays.asList(
313      KINGDOM,
314      PHYLUM,
315      CLASS,
316      ORDER,
317      FAMILY,
318      GENUS,
319      SPECIES
320    ));
321
322  /**
323   * An ordered list of all ranks that appear in Darwin Core with their own term.
324   */
325  public static final List<Rank> DWC_RANKS = Collections.unmodifiableList(
326    Arrays.asList(
327      KINGDOM,
328      PHYLUM,
329      CLASS,
330      ORDER,
331      FAMILY,
332      GENUS,
333      SUBGENUS,
334      SPECIES
335    ));
336
337  /**
338   * A set of ranks which cannot clearly be compared to any other rank as they represent rank "ranges".
339   * For example a subgeneric rank is anything below genus,
340   * so one cannot say if its higher or lower than a species for example.
341   */
342  private static final Set<Rank> UNCOMPARABLE_RANKS = Collections.unmodifiableSet(
343    new HashSet<>(Arrays.asList(
344      INFRAGENERIC_NAME,
345      INFRASPECIFIC_NAME,
346      INFRASUBSPECIFIC_NAME,
347      OTHER,
348      UNRANKED
349    )));
350
351  private static final Set<Rank> LEGACY_RANKS = Collections.unmodifiableSet(
352    new HashSet<>(Arrays.asList(
353      MORPH,
354      ABERRATION,
355      NATIO,
356      RACE,
357      PROLES,
358      CONVARIETY
359    )));
360
361  private static final Map<Rank, NomenclaturalCode> RANK2CODE;
362
363  static {
364    Map<Rank, NomenclaturalCode> rank2code = new HashMap<>();
365    rank2code.put(PARVCLASS, NomenclaturalCode.ZOOLOGICAL);
366    rank2code.put(MAGNORDER, NomenclaturalCode.ZOOLOGICAL);
367    rank2code.put(GRANDORDER, NomenclaturalCode.ZOOLOGICAL);
368    rank2code.put(PARVORDER, NomenclaturalCode.ZOOLOGICAL);
369    rank2code.put(SUPERLEGION, NomenclaturalCode.ZOOLOGICAL);
370    rank2code.put(LEGION, NomenclaturalCode.ZOOLOGICAL);
371    rank2code.put(SUBLEGION, NomenclaturalCode.ZOOLOGICAL);
372    rank2code.put(INFRALEGION, NomenclaturalCode.ZOOLOGICAL);
373    rank2code.put(SUPERCOHORT, NomenclaturalCode.ZOOLOGICAL);
374    rank2code.put(COHORT, NomenclaturalCode.ZOOLOGICAL);
375    rank2code.put(SUBCOHORT, NomenclaturalCode.ZOOLOGICAL);
376    rank2code.put(INFRACOHORT, NomenclaturalCode.ZOOLOGICAL);
377    rank2code.put(MORPH, NomenclaturalCode.ZOOLOGICAL);
378    rank2code.put(ABERRATION, NomenclaturalCode.ZOOLOGICAL);
379    rank2code.put(NATIO, NomenclaturalCode.ZOOLOGICAL);
380
381    rank2code.put(RACE, NomenclaturalCode.BOTANICAL);
382    rank2code.put(PROLES, NomenclaturalCode.BOTANICAL);
383    rank2code.put(SECTION, NomenclaturalCode.BOTANICAL);
384    rank2code.put(SUBSECTION, NomenclaturalCode.BOTANICAL);
385    rank2code.put(SERIES, NomenclaturalCode.BOTANICAL);
386    rank2code.put(SUBSERIES, NomenclaturalCode.BOTANICAL);
387
388    rank2code.put(CULTIVAR, NomenclaturalCode.CULTIVARS);
389    rank2code.put(CULTIVAR_GROUP, NomenclaturalCode.CULTIVARS);
390    rank2code.put(CONVARIETY, NomenclaturalCode.CULTIVARS);
391    rank2code.put(GREX, NomenclaturalCode.CULTIVARS);
392
393    rank2code.put(PATHOVAR, NomenclaturalCode.BACTERIAL);
394    rank2code.put(BIOVAR, NomenclaturalCode.BACTERIAL);
395    rank2code.put(CHEMOVAR, NomenclaturalCode.BACTERIAL);
396    rank2code.put(MORPHOVAR, NomenclaturalCode.BACTERIAL);
397    rank2code.put(PHAGOVAR, NomenclaturalCode.BACTERIAL);
398    rank2code.put(SEROVAR, NomenclaturalCode.BACTERIAL);
399    rank2code.put(CHEMOFORM, NomenclaturalCode.BACTERIAL);
400    rank2code.put(FORMA_SPECIALIS, NomenclaturalCode.BACTERIAL);
401
402    RANK2CODE = Collections.unmodifiableMap(rank2code);
403  }
404
405  private final String marker;
406
407  Rank() {
408    this(null);
409  }
410
411  Rank(String marker) {
412    this.marker = marker;
413  }
414
415  public String getMarker() {
416    return marker;
417  }
418
419  /**
420   * @return true for infraspecific ranks.
421   */
422  public boolean isInfraspecific() {
423    return this != SPECIES && isSpeciesOrBelow();
424  }
425
426  /**
427   * @return true for infra subspecific ranks.
428   */
429  public boolean isInfrasubspecific() {
430    return ordinal() > SUBSPECIES.ordinal() && notOtherOrUnknown();
431  }
432
433  /**
434   * @return true for rank is below genus. Also incluse species and infraspecific ranks
435   */
436  public boolean isInfrageneric() {
437    return ordinal() > GENUS.ordinal() && notOtherOrUnknown();
438  }
439
440  /**
441   * @return true for real infrageneric ranks with an infragenericEpithet below genus and above species aggregate.
442   */
443  public boolean isInfragenericStrictly() {
444    return isInfrageneric() && ordinal() < SPECIES_AGGREGATE.ordinal();
445  }
446
447  /**
448   * True for all mayor Linnéan ranks, ie kingdom,phylum,class,order,family,genus and species.
449   */
450  public boolean isLinnean() {
451    for (Rank r : LINNEAN_RANKS) {
452      if (r == this) {
453        return true;
454      }
455    }
456    return false;
457  }
458
459  public boolean isSpeciesOrBelow() {
460    return ordinal() >= SPECIES.ordinal() && notOtherOrUnknown();
461  }
462
463  public boolean isSpeciesAggregateOrBelow() {
464    return ordinal() >= SPECIES_AGGREGATE.ordinal() && notOtherOrUnknown();
465  }
466
467  public boolean notOtherOrUnknown() {
468    return this != OTHER && this != UNRANKED;
469  }
470
471  /**
472   * @return true if the rank is above genus.
473   */
474  public boolean isSuprageneric() {
475    return ordinal() < GENUS.ordinal();
476  }
477
478  /**
479   * @return true if the rank is above rank species.
480   */
481  public boolean isSupraspecific() {
482    return ordinal() < SPECIES.ordinal();
483  }
484
485  /**
486   * True for names of informal ranks that represent a range of ranks really and therefore cannot safely be compared to
487   * other ranks in all cases.
488   * Example ranks are INFRASPECIFIC_NAME or INFRAGENERIC_NAME
489   *
490   * @return true if uncomparable
491   */
492  public boolean isUncomparable() {
493    return UNCOMPARABLE_RANKS.contains(this);
494  }
495
496  /**
497   * @return true if the rank is considered a legacy rank not used anymore in current nomenclature.
498   */
499  public boolean isLegacy() {
500    return LEGACY_RANKS.contains(this);
501  }
502
503  /**
504   * @return the nomenclatural code if the rank is restricted to just one code or null otherwise
505   */
506  public NomenclaturalCode isRestrictedToCode() {
507    return RANK2CODE.get(this);
508  }
509
510  /**
511   * @return true if this rank is higher than the given other
512   */
513  public boolean higherThan(Rank other) {
514    return ordinal() < other.ordinal();
515  }
516}