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.vocabulary.Extension;
019import org.gbif.api.vocabulary.Kingdom;
020import org.gbif.api.vocabulary.Language;
021import org.gbif.api.vocabulary.NameUsageIssue;
022import org.gbif.api.vocabulary.Origin;
023import org.gbif.api.vocabulary.Rank;
024
025import java.util.Date;
026import java.util.Map;
027import java.util.UUID;
028import javax.validation.constraints.Max;
029import javax.validation.constraints.Min;
030import javax.validation.constraints.NotNull;
031import javax.validation.constraints.Past;
032
033import com.google.common.base.Objects;
034import com.google.common.collect.Maps;
035
036/**
037 * Simple metrics about a single, processed checklist dataset in time.
038 * The created timestamp with the dataset key should be unique and can be used to create graphs over time.
039 */
040public class DatasetMetrics {
041  private int key;
042  private UUID datasetKey;
043  private int usagesCount;
044  private int synonymsCount;
045  private int distinctNamesCount;
046  private int nubMatchingCount;
047  private int colMatchingCount;
048  private int nubCoveragePct;
049  private int colCoveragePct;
050  // breakdown by constituent
051  private Map<UUID, Integer> countByConstituent = Maps.newHashMap();
052  // breakdown by kingdom
053  private Map<Kingdom, Integer> countByKingdom = Maps.newHashMap();
054  // breakdown by rank
055  private Map<Rank, Integer> countByRank = Maps.newHashMap();
056  // breakdown common names by language
057  private Map<Language, Integer> countNamesByLanguage = Maps.newHashMap();
058  // number of extension records by extension
059  private Map<Extension, Integer> countExtRecordsByExtension = Maps.newHashMap();
060  // breakdown by kingdom
061  private Map<Origin, Integer> countByOrigin = Maps.newHashMap();
062  // breakdown by issue
063  private Map<NameUsageIssue, Integer> countByIssue = Maps.newHashMap();
064  // any other dynamic counts
065  private Map<String, Integer> otherCount = Maps.newHashMap();
066  private Date created;
067  private Date downloaded;
068
069
070  public int getKey() {
071    return key;
072  }
073
074  public void setKey(int key) {
075    this.key = key;
076  }
077
078  /**
079   * @return percentage of distinct names that match a name in the Catalogue of Life
080   */
081  @Max(100)
082  @Min(0)
083  public int getColCoveragePct() {
084    return colCoveragePct;
085  }
086
087  public void setColCoveragePct(int colCoveragePct) {
088    this.colCoveragePct = colCoveragePct;
089  }
090
091  @NotNull
092  public Map<UUID, Integer> getCountByConstituent() {
093    return countByConstituent;
094  }
095
096  public void setCountByConstituent(Map<UUID, Integer> countByConstituent) {
097    this.countByConstituent = countByConstituent;
098  }
099
100  @NotNull
101  public Map<Kingdom, Integer> getCountByKingdom() {
102    return countByKingdom;
103  }
104
105  public void setCountByKingdom(Map<Kingdom, Integer> countByKingdom) {
106    this.countByKingdom = countByKingdom;
107  }
108
109  @NotNull
110  public Map<Rank, Integer> getCountByRank() {
111    return countByRank;
112  }
113
114  public void setCountByRank(Map<Rank, Integer> countByRank) {
115    this.countByRank = countByRank;
116  }
117
118  /**
119   * @return the number of distinct canonical name strings
120   */
121  @Min(0)
122  public int getDistinctNamesCount() {
123    return distinctNamesCount;
124  }
125
126  public void setDistinctNamesCount(int distinctNamesCount) {
127    this.distinctNamesCount = distinctNamesCount;
128  }
129
130  @NotNull
131  public Map<Extension, Integer> getCountExtRecordsByExtension() {
132    return countExtRecordsByExtension;
133  }
134
135  public void setCountExtRecordsByExtension(Map<Extension, Integer> countExtRecordsByExtension) {
136    this.countExtRecordsByExtension = countExtRecordsByExtension;
137  }
138
139  /**
140   * @return number of records matching a name in the Catalogue of Life
141   */
142  @Min(0)
143  public int getColMatchingCount() {
144    return colMatchingCount;
145  }
146
147  public void setColMatchingCount(int colMatchingCount) {
148    this.colMatchingCount = colMatchingCount;
149  }
150
151  /**
152   * @return number of records matching a name in the GBIF backbone taxonomy
153   */
154  @Min(0)
155  public int getNubMatchingCount() {
156    return nubMatchingCount;
157  }
158
159  public void setNubMatchingCount(int nubMatchingCount) {
160    this.nubMatchingCount = nubMatchingCount;
161  }
162
163  @NotNull
164  public Map<Language, Integer> getCountNamesByLanguage() {
165    return countNamesByLanguage;
166  }
167
168  public void setCountNamesByLanguage(Map<Language, Integer> countNamesByLanguage) {
169    this.countNamesByLanguage = countNamesByLanguage;
170  }
171
172  /**
173   * @return number of records with a taxonomic status of a synonym.
174   *         For occurrence records the nub taxonomy status is used
175   */
176  @Min(0)
177  public int getSynonymsCount() {
178    return synonymsCount;
179  }
180
181  public void setSynonymsCount(int synonymsCount) {
182    this.synonymsCount = synonymsCount;
183  }
184
185  /**
186   * @return total number of name usage records in checklistbank
187   */
188  @Min(0)
189  public int getUsagesCount() {
190    return usagesCount;
191  }
192
193  public void setUsagesCount(int usagesCount) {
194    this.usagesCount = usagesCount;
195  }
196
197  /**
198   * @return map of total name usage counts by their origin
199   */
200  @NotNull
201  public Map<Origin, Integer> getCountByOrigin() {
202    return countByOrigin;
203  }
204
205  public void setCountByOrigin(Map<Origin, Integer> countByOrigin) {
206    this.countByOrigin = countByOrigin;
207  }
208
209  /**
210   * @return map of total name usage counts by their interpretation issue
211   */
212  @NotNull
213  public Map<NameUsageIssue, Integer> getCountByIssue() {
214    return countByIssue;
215  }
216
217  public void setCountByIssue(Map<NameUsageIssue, Integer> countByIssue) {
218    this.countByIssue = countByIssue;
219  }
220
221  /**
222   * @return date this metric was generated. Roughly equivalent with date of indexing
223   */
224  @NotNull
225  @Past
226  public Date getCreated() {
227    return created;
228  }
229
230  public void setCreated(Date created) {
231    this.created = created;
232  }
233
234  /**
235   * @return date new dataset data was downloaded/harvested last time
236   */
237  @NotNull
238  @Past
239  public Date getDownloaded() {
240    return downloaded;
241  }
242
243  public void setDownloaded(Date downloaded) {
244    this.downloaded = downloaded;
245  }
246
247  @NotNull
248  public UUID getDatasetKey() {
249    return datasetKey;
250  }
251
252  public void setDatasetKey(UUID datasetKey) {
253    this.datasetKey = datasetKey;
254  }
255
256  /**
257   * @return percentage of distinct names that match a name in the GBIF backbone taxonomy
258   */
259  @Max(100)
260  @Min(0)
261  public int getNubCoveragePct() {
262    return nubCoveragePct;
263  }
264
265  public void setNubCoveragePct(int nubCoveragePct) {
266    this.nubCoveragePct = nubCoveragePct;
267  }
268
269  /**
270   * For an occurrence dataset get the number of records that are interpreted to belong to a certain nub kingdom.
271   * For checklists the number of usages belonging to a certain nub kingdom. Note this is not the kingdom as explicitly
272   * given by the checklist, but the nub kingdom after matching the usages to the nub.
273   *
274   * @param kingdom to get metrics for
275   *
276   * @return the number of records found in the respective kingdom
277   */
278  @Min(0)
279  public int getCountByKingdom(Kingdom kingdom) {
280    return getCountFromMap(countByKingdom, kingdom);
281  }
282
283  /**
284   * Get the metrics by taxonomic rank, i.e. the lowest rank to which an occurrence record was identified
285   * or the rank of the name usage in a checklist.
286   *
287   * @param rank to get metrics for. Mayor Linnéan ranks only down to species plus INFRASPECIFIC_NAME.
288   *
289   * @return the number of records for the given taxonomic rank.
290   */
291  @Min(0)
292  public int getCountByRank(Rank rank) {
293    return getCountFromMap(countByRank, rank);
294  }
295
296  /**
297   * Get the metrics by name usage origin.
298   */
299  @Min(0)
300  public int getCountByOrigin(Origin origin) {
301    return getCountFromMap(countByOrigin, origin);
302  }
303
304  /**
305   * Get the metrics by name usage issue.
306   */
307  @Min(0)
308  public int getCountByIssue(NameUsageIssue issue) {
309    return getCountFromMap(countByIssue, issue);
310  }
311
312  /**
313   * Get the metrics for other dynamic counts.
314   */
315  @Min(0)
316  public int getOtherCount(String key) {
317    return getCountFromMap(otherCount, key);
318  }
319
320  /**
321   * Number of vernacular names in this checklist dataset by language.
322   * For occurrence datasets always 0.
323   *
324   * @param language of common names
325   *
326   * @return the number of records found for the extension
327   */
328  @Min(0)
329  public int getCountNamesByLanguage(Language language) {
330    return getCountFromMap(countNamesByLanguage, language);
331  }
332
333  /**
334   * Number of extension records found in the dataset.
335   *
336   * @return the number of records found for the extension
337   */
338  @Min(0)
339  public int getExtensionRecordCount(Extension extension) {
340    return getCountFromMap(countExtRecordsByExtension, extension);
341  }
342
343  public Map<String, Integer> getOtherCount() {
344    return otherCount;
345  }
346
347  public void setOtherCount(Map<String, Integer> otherCount) {
348    this.otherCount = otherCount;
349  }
350
351  /**
352   * @return value from map if key exists, 0 otherwise
353   */
354  private int getCountFromMap(Map<?, Integer> map, Object key) {
355    if (key == null) {
356      throw new IllegalArgumentException("Null not allowed");
357    }
358    if (map.containsKey(key)) {
359      return map.get(key);
360    }
361    return 0;
362  }
363
364
365  @Override
366  public boolean equals(Object object) {
367    if (this == object) {
368      return true;
369    }
370    if (!(object instanceof DatasetMetrics)) {
371      return false;
372    }
373    DatasetMetrics that = (DatasetMetrics) object;
374    return  Objects.equal(this.key, that.key)
375           && Objects.equal(this.datasetKey, that.datasetKey)
376           && Objects.equal(this.usagesCount, that.usagesCount)
377           && Objects.equal(this.synonymsCount, that.synonymsCount)
378           && Objects.equal(this.distinctNamesCount, that.distinctNamesCount)
379           && Objects.equal(this.nubMatchingCount, that.nubMatchingCount)
380           && Objects.equal(this.colMatchingCount, that.colMatchingCount)
381           && Objects.equal(this.nubCoveragePct, that.nubCoveragePct)
382           && Objects.equal(this.colCoveragePct, that.colCoveragePct)
383           && Objects.equal(this.countByConstituent, that.countByConstituent)
384           && Objects.equal(this.countByKingdom, that.countByKingdom)
385           && Objects.equal(this.countByRank, that.countByRank)
386           && Objects.equal(this.countNamesByLanguage, that.countNamesByLanguage)
387           && Objects.equal(this.countExtRecordsByExtension, that.countExtRecordsByExtension)
388           && Objects.equal(this.countByOrigin, that.countByOrigin)
389           && Objects.equal(this.countByIssue, that.countByIssue)
390           && Objects.equal(this.otherCount, that.otherCount)
391           && Objects.equal(this.created, that.created)
392           && Objects.equal(this.downloaded, that.downloaded);
393  }
394
395  @Override
396  public int hashCode() {
397    return Objects.hashCode(key, datasetKey, usagesCount, synonymsCount, distinctNamesCount, nubMatchingCount,
398      colMatchingCount, nubCoveragePct, colCoveragePct, countByConstituent, countByKingdom, countByRank,
399      countNamesByLanguage, countExtRecordsByExtension, countByOrigin, countByIssue, otherCount, created, downloaded);
400  }
401
402  @Override
403  public String toString() {
404    return Objects.toStringHelper(this)
405      .add("key", key)
406      .add("datasetKey", datasetKey)
407      .add("usagesCount", usagesCount)
408      .add("synonymsCount", synonymsCount)
409      .add("distinctNamesCount", distinctNamesCount)
410      .add("nubMatchingCount", nubMatchingCount)
411      .add("colMatchingCount", colMatchingCount)
412      .add("nubCoveragePct", nubCoveragePct)
413      .add("colCoveragePct", colCoveragePct)
414      .add("countByConstituent", countByConstituent)
415      .add("countByKingdom", countByKingdom)
416      .add("countByRank", countByRank)
417      .add("countNamesByLanguage", countNamesByLanguage)
418      .add("countExtRecordsByExtension", countExtRecordsByExtension)
419      .add("countByOrigin", countByOrigin)
420      .add("countByIssue", countByIssue)
421      .add("otherCount", otherCount)
422      .add("created", created)
423      .add("downloaded", downloaded)
424      .toString();
425  }
426}