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.common.Identifier;
019import org.gbif.api.vocabulary.IdentifierType;
020import org.gbif.api.vocabulary.ThreatStatus;
021
022import java.io.IOException;
023import java.util.List;
024import java.util.Set;
025import javax.validation.constraints.NotNull;
026
027import com.google.common.base.Objects;
028import com.google.common.base.Strings;
029import com.google.common.collect.Sets;
030import org.codehaus.jackson.JsonNode;
031import org.codehaus.jackson.annotate.JsonIgnore;
032import org.codehaus.jackson.map.DeserializationConfig;
033import org.codehaus.jackson.map.ObjectMapper;
034
035import static com.google.common.collect.Lists.newArrayList;
036
037/**
038 * An extension to a NameUsage adding all further properties that are not eagerly loaded.
039 * Use this class to store the various subresources as you need them side by side with the NameUsage.
040 * Note that properties are not automatically/lazy loaded in any way.
041 * This is just a simple container class with a few convenience methods which needs to be populated manually via its
042 * setters or the constructor.
043 */
044public class NameUsageContainer extends NameUsage {
045
046  private static final ObjectMapper MAPPER = new ObjectMapper();
047  static {
048    MAPPER.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
049  }
050
051  private List<Description> descriptions = newArrayList();
052  private List<Distribution> distributions = newArrayList();
053  private List<Identifier> identifiers = newArrayList();
054  private List<NameUsageMediaObject> media = newArrayList();
055  private List<Reference> referenceList = newArrayList();
056  private List<SpeciesProfile> speciesProfiles = newArrayList();
057  private List<NameUsage> synonyms = newArrayList();
058  private List<NameUsage> combinations = newArrayList();
059  private List<TypeSpecimen> typeSpecimens = newArrayList();
060  private List<VernacularName> vernacularNames = newArrayList();
061
062  /**
063   * Default constructor.
064   */
065  public NameUsageContainer() {
066    //empty constructor
067  }
068
069  /**
070   * Constructs a NameUsageContainer from an existing NameUsage instance.
071   */
072  public NameUsageContainer(NameUsage usage) {
073    try {
074      JsonNode propTree = MAPPER.convertValue(usage, JsonNode.class);
075      MAPPER.readerForUpdating(this).readValue(propTree);
076    } catch (IOException e) {
077      throw new IllegalStateException("Failed to copy NameUsage properties to NameUsageContainer", e);
078    }
079  }
080
081  /**
082   * @return the list of Description
083   */
084  @NotNull
085  public List<Description> getDescriptions() {
086    return descriptions;
087  }
088
089  /**
090   * @param descriptions the list of Description
091   */
092  public void setDescriptions(List<Description> descriptions) {
093    this.descriptions = descriptions;
094  }
095
096  /**
097   * @return the list of Distribution
098   */
099  @NotNull
100  public List<Distribution> getDistributions() {
101    return distributions;
102  }
103
104  /**
105   * @param distributions the Distribution list to set
106   */
107  public void setDistributions(List<Distribution> distributions) {
108    this.distributions = distributions;
109  }
110
111  /**
112   * Convenience method that analyzes all species profile records and returns the distinct list of known habitats.
113   *
114   * @return list of unique habitats
115   */
116  @NotNull
117  public Set<String> getHabitats() {
118    Set<String> habitats = Sets.newLinkedHashSet();
119    for (SpeciesProfile sp : speciesProfiles) {
120      if (!Strings.isNullOrEmpty(sp.getHabitat())) {
121        habitats.add(sp.getHabitat());
122      }
123    }
124    return habitats;
125  }
126
127  public List<NameUsageMediaObject> getMedia() {
128    return media;
129  }
130
131  public void setMedia(List<NameUsageMediaObject> media) {
132    this.media = media;
133  }
134
135  /**
136   * @return the list of all Identifier
137   */
138  @NotNull
139  public List<Identifier> getIdentifiers() {
140    return identifiers;
141  }
142
143  /**
144   * @param identifiers the Identifier list to set
145   */
146  public void setIdentifiers(List<Identifier> identifiers) {
147    this.identifiers = identifiers;
148  }
149
150  /**
151   * @return the list of all URL Identifier
152   */
153  @NotNull
154  @JsonIgnore
155  public List<Identifier> getIdentifierByType(final IdentifierType type) {
156    List<Identifier> ids = newArrayList();
157    for (Identifier i : identifiers) {
158      if (type == i.getType()) {
159        ids.add(i);
160      }
161    }
162    return ids;
163  }
164
165  /**
166   * @return the list of Reference
167   */
168  @NotNull
169  public List<Reference> getReferenceList() {
170    return referenceList;
171  }
172
173  /**
174   * @param referenceList the Reference list to set
175   */
176  public void setReferenceList(List<Reference> referenceList) {
177    this.referenceList = referenceList;
178  }
179
180  /**
181   * @return the list of SpeciesProfile
182   */
183  @NotNull
184  public List<SpeciesProfile> getSpeciesProfiles() {
185    return speciesProfiles;
186  }
187
188  /**
189   * @param speciesProfiles the SpeciesProfile list to set
190   */
191  public void setSpeciesProfiles(List<SpeciesProfile> speciesProfiles) {
192    this.speciesProfiles = speciesProfiles;
193  }
194
195  /**
196   * @return the list of synonyms
197   */
198  public List<NameUsage> getSynonyms() {
199    return synonyms;
200  }
201
202  /**
203   * @param synonyms list of synonyms
204   */
205  public void setSynonyms(List<NameUsage> synonyms) {
206    this.synonyms = synonyms;
207  }
208
209  /**
210   * @return the list of combinations known for the basionym
211   */
212  @NotNull
213  public List<NameUsage> getCombinations() {
214    return combinations;
215  }
216
217  public void setCombinations(List<NameUsage> combinations) {
218    this.combinations = combinations;
219  }
220
221    /**
222   * @return the list of TypeSpecimen
223   */
224  @NotNull
225  public List<TypeSpecimen> getTypeSpecimens() {
226    return typeSpecimens;
227  }
228
229  /**
230   * @param typeSpecimens the TypeSpecimen list to set
231   */
232  public void setTypeSpecimens(List<TypeSpecimen> typeSpecimens) {
233    this.typeSpecimens = typeSpecimens;
234  }
235
236  /**
237   * @return the list of VernacularName
238   */
239  @NotNull
240  public List<VernacularName> getVernacularNames() {
241    return vernacularNames;
242  }
243
244  /**
245   * @param vernacularNames the VernacularName list to set
246   */
247  public void setVernacularNames(List<VernacularName> vernacularNames) {
248    this.vernacularNames = vernacularNames;
249  }
250
251  /**
252   * Convenience method that analyzes all species profile records and returns the distinct list of known living
253   * periods.
254   *
255   * @return list of unique living periods
256   */
257  @NotNull
258  public Set<String> getLivingPeriods() {
259    Set<String> periods = Sets.newLinkedHashSet();
260    for (SpeciesProfile sp : speciesProfiles) {
261      if (!Strings.isNullOrEmpty(sp.getLivingPeriod())) {
262        periods.add(sp.getLivingPeriod());
263      }
264    }
265    return periods;
266  }
267
268  /**
269   * Convenience method that analyzes all distribution records and returns the distinct list of known threat status.
270   *
271   * @return list of unique threat status
272   */
273  @NotNull
274  public Set<ThreatStatus> getThreatStatus() {
275    Set<ThreatStatus> threats = Sets.newLinkedHashSet();
276    for (Distribution d : distributions) {
277      if (d.getThreatStatus() != null) {
278        threats.add(d.getThreatStatus());
279      }
280    }
281    return threats;
282  }
283
284  /**
285   * Convenience method that analyzes all species profile records for extinct.
286   * If several records contradict use the majority or if numbers are equal true.
287   *
288   * @return true if extinct
289   */
290  public Boolean isExtinct() {
291    int ctrue = 0;
292    int cfalse = 0;
293    for (SpeciesProfile sp : speciesProfiles) {
294      if (sp.isExtinct() == null) {
295        continue;
296      }
297
298      if (sp.isExtinct()) {
299        ctrue++;
300      } else if (!sp.isExtinct()) {
301        cfalse++;
302      }
303    }
304    return ctrue + cfalse == 0 ? null : ctrue >= cfalse;
305  }
306
307  /**
308   * Convenience method that analyzes all species profile records for freshwater habitat flags.
309   *
310   * @return true if freshwater
311   */
312  public Boolean isFreshwater() {
313    int ctrue = 0;
314    int cfalse = 0;
315    for (SpeciesProfile sp : speciesProfiles) {
316      if (sp.isFreshwater() == null) {
317        continue;
318      }
319
320      if (sp.isFreshwater()) {
321        ctrue++;
322      } else if (!sp.isFreshwater()) {
323        cfalse++;
324      }
325    }
326    return ctrue + cfalse == 0 ? null : ctrue >= cfalse;
327  }
328
329  /**
330   * Convenience method that analyzes all species profile records for marine habitat flags.
331   *
332   * @return true if marine
333   */
334  public Boolean isMarine() {
335    int ctrue = 0;
336    int cfalse = 0;
337    for (SpeciesProfile sp : speciesProfiles) {
338      if (sp.isMarine() == null) {
339        continue;
340      }
341
342      if (sp.isMarine()) {
343        ctrue++;
344      } else if (!sp.isMarine()) {
345        cfalse++;
346      }
347    }
348    return ctrue + cfalse == 0 ? null : ctrue >= cfalse;
349  }
350
351  /**
352   * Convenience method that analyzes all species profile records for terrestrial habitat flags.
353   *
354   * @return true if terrestrial
355   */
356  public Boolean isTerrestrial() {
357    int ctrue = 0;
358    int cfalse = 0;
359    for (SpeciesProfile sp : speciesProfiles) {
360      if (sp.isTerrestrial() == null) {
361        continue;
362      }
363
364      if (sp.isTerrestrial()) {
365        ctrue++;
366      } else if (!sp.isTerrestrial()) {
367        cfalse++;
368      }
369    }
370    return ctrue + cfalse == 0 ? null : ctrue >= cfalse;
371  }
372
373  @Override
374  public boolean equals(Object object) {
375    if (object instanceof NameUsageContainer) {
376      if (!super.equals(object)) {
377        return false;
378      }
379      final NameUsageContainer other = (NameUsageContainer) object;
380      return Objects.equal(this.descriptions, other.descriptions)
381             && Objects.equal(this.distributions, other.distributions)
382             && Objects.equal(this.media, other.media)
383             && Objects.equal(this.referenceList, other.referenceList)
384             && Objects.equal(this.speciesProfiles, other.speciesProfiles)
385             && Objects.equal(this.synonyms, other.synonyms)
386             && Objects.equal(this.typeSpecimens, other.typeSpecimens)
387             && Objects.equal(this.vernacularNames, other.vernacularNames);
388    }
389    return false;
390  }
391
392  @Override
393  public int hashCode() {
394    return Objects.hashCode(descriptions,
395                            distributions,
396                            media,
397                            referenceList,
398                            speciesProfiles,
399                            synonyms,
400                            typeSpecimens,
401                            vernacularNames);
402  }
403
404}