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.model.predicate;
015
016import org.gbif.api.annotation.Experimental;
017import org.gbif.api.model.common.search.SearchParameter;
018import org.gbif.api.util.SearchTypeValidator;
019
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Objects;
024import java.util.Optional;
025import java.util.StringJoiner;
026
027import javax.annotation.Nullable;
028import javax.validation.constraints.NotNull;
029import javax.validation.constraints.Size;
030
031import com.fasterxml.jackson.annotation.JsonCreator;
032import com.fasterxml.jackson.annotation.JsonProperty;
033
034import io.swagger.v3.oas.annotations.media.Schema;
035
036import static org.gbif.api.util.PreconditionUtils.checkArgument;
037
038/**
039 * This predicate checks if its {@code key} contains any of its {@code values}.
040 */
041@Schema(
042  description = "This predicate checks if its `key` is equal to any of its `values`."
043)
044public class InPredicate<S extends SearchParameter> implements Predicate {
045
046  @Schema(
047    description = "The search parameter to test."
048  )
049  @NotNull private final S key;
050
051  @Schema(
052    description = "The list of value to test for."
053  )
054  @NotNull
055  @Size(min = 1)
056  private final Collection<String> values;
057
058  @Schema(
059    description = "Whether to match letter case (UPPER or lower case) on string value comparisons."
060  )
061  @Experimental
062  @Nullable
063  private final Boolean matchCase;
064
065  @Schema(
066    description = "Specify which taxonomy to use."
067  )
068  @Experimental
069  @Nullable
070  private final String checklistKey;
071
072  public InPredicate(
073    S key,
074    Collection<String> values,
075    Boolean matchCase
076  ) {
077    this(key, values, matchCase, null);
078  }
079
080  @JsonCreator
081  public InPredicate(
082      @JsonProperty("key") S key,
083      @JsonProperty("values") Collection<String> values,
084      @Nullable @JsonProperty(value = "matchCase") Boolean matchCase,
085      @Nullable @JsonProperty(value = "checklistKey") String checklistKey
086    ) {
087    this.matchCase = matchCase;
088    this.checklistKey = checklistKey;
089    Objects.requireNonNull(key, "<key> may not be null");
090    Objects.requireNonNull(values, "<values> may not be null");
091    checkArgument(!values.isEmpty(), "<values> may not be empty");
092    // make sure the value is of the right type according to the key given
093    for (String value : values) {
094      if (value == null) {
095        throw new NullPointerException();
096      }
097      SearchTypeValidator.validate(key, value);
098    }
099
100    this.key = key;
101    this.values = Collections.unmodifiableList(new ArrayList<>(values));
102  }
103
104  public S getKey() {
105    return key;
106  }
107
108  public Collection<String> getValues() {
109    return values;
110  }
111
112  /**
113   * This flag enables the use of case-sensitive matches and aggregations on certain search parameters.
114   * <p>
115   * Fields that support this feature are: occurrenceId, recordedBy, samplingProtocol, catalogNumber, collectionCode,
116   * institutionCode, eventId, parentEventId, waterBody, stateProvince, recordNumber, identifiedBy, organismId and locality.
117   * <p>
118   * This is an experimental feature and its implementation map change or be removed at any time.
119   */
120  @Experimental
121  public Boolean isMatchCase() {
122    return Optional.ofNullable(matchCase).orElse(Boolean.FALSE);
123  }
124
125  @Experimental
126  public String getChecklistKey() {
127    return checklistKey;
128  }
129
130  @Override
131  public boolean equals(Object o) {
132    if (this == o) {
133      return true;
134    }
135    if (o == null || getClass() != o.getClass()) {
136      return false;
137    }
138    InPredicate<S> that = (InPredicate<S>) o;
139    return key == that.key
140      && Objects.equals(values, that.values)
141      && matchCase == that.matchCase
142      && Objects.equals(checklistKey, that.checklistKey);
143  }
144
145  @Override
146  public int hashCode() {
147    return Objects.hash(key, values, matchCase, checklistKey);
148  }
149
150  @Override
151  public String toString() {
152    return new StringJoiner(", ", InPredicate.class.getSimpleName() + "[", "]")
153      .add("key=" + key)
154      .add("values=" + values)
155      .add("matchCase=" + matchCase)
156      .add("checklistKey=" + checklistKey)
157      .toString();
158  }
159}