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