001/*
002 * Copyright 2020-2021 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.util;
017
018import org.gbif.api.model.common.LinneanClassification;
019import org.gbif.api.model.common.LinneanClassificationKeys;
020import org.gbif.api.vocabulary.Rank;
021
022import java.util.LinkedHashMap;
023import java.util.Objects;
024import java.util.stream.Collectors;
025import java.util.stream.Stream;
026
027import javax.annotation.Nullable;
028import javax.validation.constraints.NotNull;
029
030import org.apache.commons.lang3.StringUtils;
031
032@SuppressWarnings("unused")
033public class ClassificationUtils {
034
035  /**
036   * Concatenates all higher Linnean taxa into a single dwc:higherClassification string, skipping
037   * null values.
038   *
039   * @param lc the LinneanClassification to join into the higher classification string
040   *
041   * @return the concatenated dwc:higherClassification string
042   */
043  @Nullable
044  public static String getHigherClassification(LinneanClassification lc) {
045    return StringUtils.trimToNull(
046      Stream.of(lc.getKingdom(), lc.getPhylum(), lc.getClazz(),
047        lc.getOrder(), lc.getFamily(), lc.getGenus())
048        .filter(Objects::nonNull)
049        .collect(Collectors.joining(", ")));
050  }
051
052  /**
053   * Gets a higher taxon property by passing the rank of it.
054   * Only Linnean ranks are supported.
055   *
056   * @param lc   the LinneanClassification holding the taxon property to be retrieved
057   * @param rank the higher linnean rank to retrieve
058   *
059   * @return the name of the higher taxon or null if rank doesnt exist
060   */
061  @Nullable
062  public static String getHigherRank(LinneanClassification lc, Rank rank) {
063    if (rank != null) {
064      switch (rank) {
065        case KINGDOM:
066          return lc.getKingdom();
067        case PHYLUM:
068          return lc.getPhylum();
069        case CLASS:
070          return lc.getClazz();
071        case ORDER:
072          return lc.getOrder();
073        case FAMILY:
074          return lc.getFamily();
075        case GENUS:
076          return lc.getGenus();
077        case SUBGENUS:
078          return lc.getSubgenus();
079        case SPECIES:
080          return lc.getSpecies();
081      }
082    }
083    return null;
084  }
085
086  /**
087   * Sets a higher taxon property by passing the rank of it.
088   *
089   * @param lc   the LinneanClassification on which to set the new property
090   * @param rank the higher linnean rank to set
091   * @param name the higher ranks name
092   */
093  public static void setHigherRank(LinneanClassification lc, Rank rank, String name) {
094    if (rank != null) {
095      switch (rank) {
096        case KINGDOM:
097          lc.setKingdom(name);
098          break;
099        case PHYLUM:
100          lc.setPhylum(name);
101          break;
102        case CLASS:
103          lc.setClazz(name);
104          break;
105        case ORDER:
106          lc.setOrder(name);
107          break;
108        case FAMILY:
109          lc.setFamily(name);
110          break;
111        case GENUS:
112          lc.setGenus(name);
113          break;
114        case SUBGENUS:
115          lc.setSubgenus(name);
116          break;
117        case SPECIES:
118          lc.setSpecies(name);
119          break;
120      }
121    }
122  }
123
124  /**
125   * An ordered map with entries for all higher Linnean ranks down to species which are not null.
126   * The map starts with the highest rank, e.g. the kingdom and maps the name usage key to its canonical name.
127   *
128   * @param lc the object that implements both LinneanClassification and LinneanClassificationKeys from which to build
129   *           the map
130   *
131   * @return map of higher ranks
132   */
133  @NotNull
134  public static <T extends LinneanClassification & LinneanClassificationKeys> LinkedHashMap<Integer, String> getHigherClassificationMap(
135    T lc) {
136    return getHigherClassificationBaseMap(lc);
137  }
138
139  /**
140   * An ordered map with entries for all higher Linnean ranks down to the actual direct parent of this usage.
141   * The map starts with the highest rank, e.g. the kingdom and maps the name usage key to its canonical name.
142   * The name usage itself is never included, even though a higher rank might point to the usage itself.
143   *
144   * @param lc the object that implements both LinneanClassification and LinneanClassificationKeys from which to build
145   *           the map
146   *
147   * @return map of higher ranks
148   */
149  @NotNull
150  public static <T extends LinneanClassification & LinneanClassificationKeys> LinkedHashMap<Integer, String> getHigherClassificationMap(
151    T lc, int key, Integer parentKey, String parent) {
152    LinkedHashMap<Integer, String> map = getHigherClassificationBaseMap(lc);
153
154    if (parentKey != null) {
155      map.put(parentKey, parent);
156    }
157    // remove notion to this usage
158    map.remove(key);
159
160    return map;
161  }
162
163  private static <T extends LinneanClassification & LinneanClassificationKeys> LinkedHashMap<Integer, String> getHigherClassificationBaseMap(
164    T lc) {
165    LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
166    if (lc.getKingdomKey() != null) {
167      map.put(lc.getKingdomKey(), lc.getKingdom());
168    }
169    if (lc.getPhylumKey() != null) {
170      map.put(lc.getPhylumKey(), lc.getPhylum());
171    }
172    if (lc.getClassKey() != null) {
173      map.put(lc.getClassKey(), lc.getClazz());
174    }
175    if (lc.getOrderKey() != null) {
176      map.put(lc.getOrderKey(), lc.getOrder());
177    }
178    if (lc.getFamilyKey() != null) {
179      map.put(lc.getFamilyKey(), lc.getFamily());
180    }
181    if (lc.getGenusKey() != null) {
182      map.put(lc.getGenusKey(), lc.getGenus());
183    }
184    if (lc.getSpeciesKey() != null) {
185      map.put(lc.getSpeciesKey(), lc.getSpecies());
186    }
187    return map;
188  }
189
190  /**
191   * Gets a higher taxon key by passing the rank of it.
192   * Only Linnean ranks are supported.
193   *
194   * @param lck  the classification that holds the taxon key
195   * @param rank the higher linnean rank to retrieve
196   *
197   * @return the key of the higher taxon or null if rank doesnt exist
198   */
199  @Nullable
200  public static Integer getHigherRankKey(LinneanClassificationKeys lck, Rank rank) {
201    if (rank != null) {
202      switch (rank) {
203        case KINGDOM:
204          return lck.getKingdomKey();
205        case PHYLUM:
206          return lck.getPhylumKey();
207        case CLASS:
208          return lck.getClassKey();
209        case ORDER:
210          return lck.getOrderKey();
211        case FAMILY:
212          return lck.getFamilyKey();
213        case GENUS:
214          return lck.getGenusKey();
215        case SUBGENUS:
216          return lck.getSubgenusKey();
217        case SPECIES:
218          return lck.getSpeciesKey();
219      }
220    }
221    return null;
222  }
223
224  /**
225   * Sets a higher taxon property by passing the rank of it.
226   *
227   * @param lck      the classification on which to set the taxon property
228   * @param rank     the higher rank to set
229   * @param usageKey key of the higher ranks usage
230   */
231  public static void setHigherRankKey(LinneanClassificationKeys lck, Rank rank, Integer usageKey) {
232    if (rank != null) {
233      switch (rank) {
234        case KINGDOM:
235          lck.setKingdomKey(usageKey);
236          break;
237        case PHYLUM:
238          lck.setPhylumKey(usageKey);
239          break;
240        case CLASS:
241          lck.setClassKey(usageKey);
242          break;
243        case ORDER:
244          lck.setOrderKey(usageKey);
245          break;
246        case FAMILY:
247          lck.setFamilyKey(usageKey);
248          break;
249        case GENUS:
250          lck.setGenusKey(usageKey);
251          break;
252        case SUBGENUS:
253          lck.setSubgenusKey(usageKey);
254          break;
255        case SPECIES:
256          lck.setSpeciesKey(usageKey);
257          break;
258      }
259    }
260  }
261
262  /**
263   * Sets a higher taxon property by passing the rank of it.
264   *
265   * @param lc       the object that implements both LinneanClassification and LinneanClassificationKeys on which to set
266   *                 the taxon property
267   * @param rank     the higher linnean rank to set
268   * @param name     the higher ranks name
269   * @param usageKey key of the higher ranks usage
270   */
271  public static <T extends LinneanClassification & LinneanClassificationKeys> void setHigherRank(
272    T lc, Rank rank, String name, Integer usageKey
273  ) {
274    setHigherRank(lc, rank, name);
275    setHigherRankKey(lc, rank, usageKey);
276  }
277
278  /**
279   * @return true if at least one higher rank of the classification is given, i.e. not null or empty
280   */
281  public static boolean hasContent(LinneanClassification lc) {
282    for (Rank qr : Rank.DWC_RANKS) {
283      if (StringUtils.isNotEmpty(getHigherRank(lc, qr))) {
284        return true;
285      }
286    }
287    return false;
288  }
289
290  /**
291   * Copies all linnean classification based higher taxon keys from one instance to another.
292   */
293  public static void copyLinneanClassificationKeys(LinneanClassificationKeys source, LinneanClassificationKeys target) {
294    for (Rank r : Rank.DWC_RANKS) {
295      setHigherRankKey(target, r, source.getHigherRankKey(r));
296    }
297  }
298
299  /**
300   * Copies all linnean classification based higher taxon names from one instance to another.
301   */
302  public static void copyLinneanClassification(LinneanClassification source, LinneanClassification target) {
303    for (Rank r : Rank.DWC_RANKS) {
304      setHigherRank(target, r, source.getHigherRank(r));
305    }
306  }
307
308  /**
309   * Utility class.
310   */
311  private ClassificationUtils() {
312  }
313}